为什么写

在我编写js代码中,关于处理二进制数据了解甚少,好像都是用数组表示,但是成员又很模糊。尤其是在遇到一些http的post请求或websocket,发送二进制数据(字节)时,还有一些算法的翻译,数据的转化,协议的复现,都需要不断的从网络上查阅,并未系统的从文档教程中入手。于是写这篇的目的就是为了加固对二进制数据的理解,以及JavaScript中如何操作二进制数据的。

ArrayBuffer

其他语言java,易所表示的是字节数组,字节集,而在js中则称二进制数组(都是用来表示二进制数据的),要注意的是这里的二进制数组并不是真正的数组,而是类似数组的对象。(后文会提到)

存储二进制数据用到的就是ArrayBuffer,但 ArrayBuffer不能直接读写,只能存储,需要通过视图来进行操作。

例如存储二进制数据的则是ArrayBuffer对象,例如请求图片时,就会指定参数 responseType: 'arraybuffer'表示返回二进制数据,也就是图片数据。

ArrayBuffer也是一个构造函数,可以分配一段可以存放数据的连续内存区域。

const buffer = new ArrayBuffer(8);
ArrayBuffer {
  [Uint8Contents]: <00 00 00 00 00 00 00 00>,
  byteLength: 8
}

这里的buffer.byteLength属性用于获取字节长度(返回32),直接打印buf的结果

其中还有一个slice方法,允许将内存区域的一部分,拷贝生成一个新的ArrayBuffer对象。下面代码拷贝buffer对象的前 3 个字节(从 0 开始,到第 3 个字节前面结束)

const buffer = new ArrayBuffer(8);
const newBuffer = buffer.slice(0, 3);

除了slice方法,ArrayBuffer对象不提供任何直接读写内存的方法,只允许在其上方建立视图,然后通过视图读写。

TypedArray

不过只有空数据可没用,肯定需要操作ArrayBuffer,也就要介绍下TypedArray

ArrayBuffer对象作为内存区域,可以存放多种类型的数据。同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view),ArrayBuffer有两种视图,一种是TypedArray视图,另一种是DataView视图。这里只介绍TypedArray

TypedArray视图一共包括 9 种类型,每一种视图都是一种构造函数通过9个构造函数,可以生成9种数据格式的视图,比如Uint8Array(无符号8位整数,表示一个字节)数组视图,具体如下

数据类型

字节长度

含义

对应的C语言类型

Int8

1

8位带符号整数

signed char

Uint8

1

8位不带符号整数

unsigned char

Uint8C

1

8位不带符号整数(自动过滤溢出)

unsigned char

Int16

2

16位带符号整数

short

Uint16

2

16位不带符号整数

unsigned short

Int32

4

32位带符号整数

int

Uint32

4

32位不带符号的整数

unsigned int

Float32

4

32位浮点数

float

Float64

8

64位浮点数

double

视图的构造函数可以接受三个参数:

  • 第一个参数(必需):视图对应的底层ArrayBuffer对象。
  • 第二个参数(可选):视图开始的字节序号,默认从 0 开始。
  • 第三个参数(可选):视图包含的数据个数,默认直到本段内存区域结束。

演示

不妨给它写入字符串abc,对应的十进制ASCII码为97,98,99,由于ASCII码占用一个字节存储,所以这里选择Uint8Array用于表示

const buffer = new ArrayBuffer(8);
const buf = new Uint8Array(buffer);
buf.set([97, 98, 99]);
console.log(buf.buffer); 

// 输出结果
ArrayBuffer {
  [Uint8Contents]: <61 62 63 00 00 00 00 00>,
  byteLength: 8
}

可以看到abc确实存入了,并用十六进制的形式表示,为了验证,这里使用NodeJS中的Buffer来演示,当然也可以使用原生的TextEncoder

Buffer.from(buf.buffer).toString() // abc

你也可以直接通过数组下标的形式,来访问数据,如buf[0]返回的就是97,但buf又有lengt与其他的属性方法,这种数组就统称为类数组。

buf还有一些方法,无非就是操作字节复制,偏移就不做过多介绍与演示了,具体文档可查看

NodeJS的Buffer

buffer 缓冲区 | Node.js API 文档 (nodejs.cn)

在Nodejs中有专门的操作ArrayBuffer 的对象BufferBuffer 类是 JavaScript Uint8Array 类的子类

所以Uint8Array有的属性方法Buffer也有,不过Nodejs对Buffer增加了额外的方法供开发者调用。

Buffer.from

上面的代码 Buffer.from(buf.buffer).toString(),也就是ArrayBuffer 数据转为utf8编码文本。其中toString还能转为以下编码(toString默认utf8)

type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex';

不过Nodejs不支持gbk编码,所以需要使用第三方包,如iconv-lite

Buffer.from()有多个方法实现,第一个参数可以传入ArrayBuffer | Uint8Array | string,如果是string类型,第二个参数为编码格式,例如实现编码转化

// base64
Buffer.from(str).toString('base64'); // 将str转base64编码
Buffer.from(str, 'base64').toString(); // 将base64编码转str

// hex
Buffer.from(str).toString('hex');  // 将str转hex编码
Buffer.from(str, 'hex').toString(); // 将hex编码转str

封装Base64编码与解码

const Base64 = {
  encode: (str) => {
    return Buffer.from(str).toString('base64');
  },
  decode: (str) => {
    return Buffer.from(str, 'base64').toString();
  }
}

buf.toJSON()

将会得到buf的视图类型,与二进制数组。

// let buf = Buffer.from('abc');
let buf = Buffer.from([97,98,99]);
console.log(buf); // <Buffer 61 62 63>

buf.toJSON() // { type: 'Buffer', data: [ 97, 98, 99 ] }
// 效果等同于 JSON.stringify(buf);

buf.values() // [ 97, 98, 99 ]   可以直接得到二进制数据

官方文档: buffer 缓冲区 | Node.js API 文档 (nodejs.cn)

ArrayBuffer 和 Buffer 区别

上述对这两者进行了介绍,这里总结一下

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,是一个字节数组,可读但不可直接写。

Buffer 是 Node.JS 中用于操作 ArrayBuffer 的视图,继承自Uint8Array,是 TypedArray 的一种。

通俗点来说(对我而言),ArrayBuffer相当于其他语言的字节数组、字节集,但不可写,而Buffer 对象则是操作ArrayBuffer的。

应用

与二进制数据有关的地方就有应用

编码转化

将请求图片转化成base64编码

axios
  .get('图片url地址', {
    responseType: 'arraybuffer',
  })
  .then((res) => {
    let base64Img = res.data.toString('base64');
    console.log(base64Img);
  });

在axios请求图片数据的时候,指定responseType: 'arraybuffer',返回的data就是一个buffer对象。(当时写成这样的代码 Buffer.from(res.data).buffer,不过不妨碍)

http发送二进制数据与WebSocket

axios.post('http://example.com', Buffer.from('abc')).then((res) => {
  console.log(res.data);
});
let socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';

// Wait until socket is open
socket.addEventListener('open', function (event) {
  // Send binary data
  const typedArray = new Uint8Array(4);
  socket.send(typedArray.buffer);
});

// Receive binary data
socket.addEventListener('message', function (event) {
  const arrayBuffer = event.data;
  // ···
});

文件读写

等等。。。

参考

ArrayBuffer - ECMAScript 6入门 (ruanyifeng.com)

ArrayBuffer 和 Buffer 有何区别? - 知乎 (zhihu.com)