-
Notifications
You must be signed in to change notification settings - Fork 0
Description
在读《深入浅出 Node.js》时看到 Buffer 这一章,并不是十分理解。工作中也遇到过诸如加解密,处理二进制数据之类的东东。于是从 ES6 的一个新 API
ArrayBuffer开始来捋一捋。
ArrayBuffer 是什么
ArrayBuffer对象用来表示通用的、固定长度的原始二进制数据缓冲区,不能直接操作,需要通过TypedArray或者DataView视图进行操作。视图的作用是以指定的格式解读二进制数据。
Buffer是 Node 中以一种更优化、更适合 Node.js 用例的方式对Uint8ArrayAPI 的实现。
Uint8Array是TypedArray已有的9种类型之一(其他类型具体可见 TypedArray)
为什么需要这种数据类型
性能 当 JS 与操作系统之间发生大量的、实时的数据交换时,它们之间的数据通信必须是二进制的。否则两端的 JavaScript 脚本(浏览器 or Node)和操作系统(显卡 or 文件I/O,网络 I/O等)都要进行转化,将会十分损耗性能。ArrayBuffer 接口的原始设计目的与 WebGL 项目有关。
如何使用
在 C 语言中,我们可以通过数组的下标直接操作内存。而 JavaScript 则提供了 ArrayBuffer 这个与操作系统的原生接口进行二进制通信。需要注意的是,ArrayBuffer 本身只代表一小段缓存区,可以存放以任何格式解读的内容(8位/16位/32位/64位/原码/补码/有符号/无符号),具体的解读和设置需要通过视图 来操作,视图类似于数组(但与实际的数组有区别,比如元素数值只能在某个确定的范围内)。
比如说创建一个有 12 bytes 的内存(也就是 96 bits):
const buffer = new ArrayBuffer(12)以 TypedArray 为例,可以建立 32 位带符号整数 Int32Array 构造函数来操作和解读(因此数组的长度会是 3,元素范围在 -2^31~(2^31)-1 之间):
const x1 = new Int32Array(buffer) // => Int32Array(3) [0, 0, 0]
x1[0] = 1;也可以建立 8 位不带符号整数 Uint8Array 构造函数来操作解读(因此数组长度会是 12 位,元素范围在 0~255 之间):
const x2 = new Int8Array(buffer) // => Int8Array(12) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
x2[0] = 2;需要注意的是,由于操作的是同一段内存,一个视图对底层内存的修改会影响到另一个视图。
x1[0] // 2除了接受 ArrayBuffer 实例作为参数,TypedArray 视图的构造函数还可以接受普通数组作为参数,直接分配内存生成底层的ArrayBuffer实例,并同时完成对这段内存的赋值。
const typedArray = new Uint8Array([0,1,2]);
typedArray.length // 3
typedArray[0] = 5;
typedArray // [5, 1, 2]这里只是简单的介绍,细致的 API 可以移步 ArrayBuffer
Buffer(Node.js)
在 ECMAScript 2015 引入 TypedArray 之前,JavaScript 语言没有读取或操作二进制数据流的机制。 Buffer 类被引入作为 Node.js API 的一部分,使其可以在 TCP 流或文件系统操作等场景中处理二进制数据流。
与 TypedArray 的关系
Buffer 实例也是 Uint8Array 实例,但是与 ES6 规范还是有微妙的不同。具体参见 Buffer and TypedArray
如何使用
与 Uint8Array 相似,Buffer 对象类似于数组,元素为 16 进制的两位数,即 0 到 255 的数值。由于 new Buffer API 已废弃,一般使用 Buffer.from() 来创建新 buffer 对象。Uint8Array 也有 from 方法,只不过参数必须为数组或可迭代的对象;而 Buffer.from() 则提供了如 array,arrayBuffer,buffer, string(需提供 encoding,默认 utf-8) 等多种选项。
例如:
const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); // <Buffer 62 75 66 66 65 72>
buf.toString() // 'buffer'
buf[0] // 98 (0x62 的10进制)
buf[0] = 0x61;
buf.toString() // 'auffer'
const buf1 = Buffer.from('this is a tést'); // <Buffer 74 68 69 73 20 69 73 20 61 20 74 c3 a9 73 74>
buf1.toString() // 'this is a tést'这里只列举了最简单的使用方法,更多操作请移步 Buffers API Encodings
如上,Buffer 对象可以与字符串之间相互转换,目前支持的字符串编码类型有:
- ascii
- utf8
- utf16le
- ucs2
- base64
- latin1
- hex
详情可参见 Buffers and Character Encodings
注意事项
Buffer 在使用场景中,通常是以一段一段的方式传输,在拼接时宽字节字符串有可能被截断,正确的处理方式应该是将多个小 Buffer 对象拼接为一个 Buffer 对象,例如使用 Buffer.concat(chunks, size)。(chunks 元素为多个小 buffer 对象,size 为 Buffer 总长度)
总结
之前容易搞混的是内存,Buffer/TypedArray,实际内容(比如传入的数组/字符串等)之间的关系。还有 ArrayBuffer 的字节长等等都是什么鬼,这里以自己的方式总结一下:
ArrayBuffer代表的是缓存,按照实例化时传入的length向系统申请内存,length 即申请的内存的字节长度- 申请完内存后要往内存里存储/操作数据了,就像 C 语言里是通过
*或者数组下标来操作内存中存储的内容一样,JS 中可以通过与刚才创建的ArrayBuffer对应的TypedArray或者DataView来操作这段内存中存储的内容(本篇文章没有涉及DataView的操作,不过也比较相似) TypedArray的使用就和 C 语言的数组比较类似了,创建实例时内存中就会在对应地址存储相应的值,也可以通过数组下标来改变存储内容Buffer是对TypedArray中Uint8Array在 Node 里的实现,当从字符串创建Buffer时,实际存储的内容与字符串属于编码关系