全部文章

JavaScript 字符串, 二进制数据 以及 Base64

2018-02-04 @sunderls

js binary

最近学习bluetooth API,发现其涉及到很多二进制数据操作,这里做一些学习笔记。

atob, btoa, base64

先从window.atob()window.btoa()来看看这两个方法都做了什么。

根据 mdn中 btoa()的定义, atob和btoa是用来base64编码解码的。

什么是base64?

嗯,感觉就是说比如网页的http协议全名 hypertext transfer protocol, 从名字上就可以看出来其基于的是文本text,为啥是文本呢?貌似有很多好处,看这里)。

那http协议怎么传输文件和图片呢?这些可不是文字啊, 这就需要把这些二进制数据通过某种一对一关系转换为文字。

因为计算机世界本质上都是0和1,那我们每8个bit算一个文字的话,刚好就可以用asc码表示了么?不对,最开始的asc只有7位,所以7和8不是倍数关系,就没有被采用。

加上不同系统对于特殊字符的处理不通,所以Base64就被采用了,它只用了64个安全的字符。可以看出64是6个bit,比8小2个bit,下面说一下原理(wikipedia)。

比如一个字符串 Man

字符 M a n
asc码 77 97 110
二进制表示 01001101 01100001 01101110

由于base64是取6个bit,所以得到如下4个部分

二进制表示 010011 010110 000101 101110
base64对应字符 T W F u

所以得到了TWFu,可以在console里测试一下:

console.assert(btoa('Man') === 'TWFu')

所以Base64是把3个字符转化位4个字符,所以长度增加了。 如果字节不能被3整除的话就加0凑够,0对应的是=,所以经常在url或者邮件里面里面经常看到==

嗯,这就是base64的大概说明了。

data url

Data URL也是用的base64,其结构是

data:[<mediatype>][;base64],<data>

我们测试以下,比如我们写一个文本文件,里面是Man,拼装成如下url

data:text/plain;base64,TWFu

复制以上链接,粘贴到浏览器地址栏访问可以看到显示了Man

ArrayBuffer

但是atobbtoa是字符串的操作,对于二进制数据的话,首先要得到其字符串才可以啊。

如果是File的话,可以用FileReader;如果是Canvas可以用Canvas.toDataURL();

JavaScript已经支持了ArrayBuffer,用来存储一般的二进制数据, 但是不能直接读取和操作,因为它是一般的二进制数据,要读的话需要按照某种格式DataView来才行,或者用某种固定格式的TypedArray,比如8个bit一个一个的Int8Array或者UInt8Array。

嗯,还是来看 'Man'这个例子。

// 三个字节,24个bit
const buffer = new Int8Array(3);
buffer[0] = 77;
buffer[1] = 97;
buffer[2] = 110;

这个buffer就是Man的二进制表示了,chrome最新版的话支持了TextDecoderTextEncoder,我们也可以试试

const encoder = new TextEncoder();
const encoded = encoder.encode('Man');
buffer.forEach((num,i) => {
    console.assert(buffer[i] === encoded[i]);
});

const decoder = new TextDecoder();
console.assert(decoder.decode(buffer) === 'Man');

如果不支持TextDecoder呢,这个也很简单,倒回去把每8个bit转换成string就行了。(仅限于基础asc字符)

这个转换用String.fromCharCode:

String.fromCharCode(77)
// 'M'

如果是直接用原始的ArrayBuffer呢?

//初始化一个24bit的数据
const buffer = new ArrayBuffer(24);

//初始化一个DataView
const view = new DataView(buffer,0,24); 

// 在不同的offset上,设置bit数据
view.setInt8(0, 77);
view.setInt8(8, 97);
view.setInt8(16, 110);

// 可以看到这时候的buffer和之前的buffer是一样的。
const decoder = new TextDecoder();
console.assert(decoder.decode(buffer) === 'Man');

总结

ArrayBuffer是二进制数据,因为是low level的数据,需要用DataView来操作。 TypedArray是在其之上的便利的抽象格式,比较方便。

二进制数据每8个bit转换成数字的话,可以得到字符串,处理的话可以自己一个一个遍历,也可以用TextDecoder。

但是字符串有Unicode的问题,所以严格按照8个bit的话是有问题的。关于unicode的话,可以参考我的另外一篇文章深入了解js的正则表达式