计算机就是处理 0 和 1,很尴尬的是在引入 TypedArray 之前,JavaScript 没有操作二进制数据流的机制,Buffer 类用一种更适合 Node.js 的方式实现了 Uint8Array API,用于在 TCP 流、文件系统操作等场景处理二进制字节

bit 与 Byte

  1. bit 是我们常说的比特,比特币就是以此命名的,bit 是二进制的最小信息单位,1 bit 就是我们说的 1 位,64 位操作系统 CPU 一次能处理 Buffer - 图1 位的数据
  2. Byte 被翻译为字节,是计量存储或者传输流量的单位,硬盘容量、网速等说的都是字节,一个英文字符是一个字节,也就是我们说的 1B,中文字符通常是两个字节(Node.js 中使用三个字节)
    1. 1 byte = 8 bit
    Node.js 的 Buffer 的单位是 1 byte,意味着存储着 8 个 bit,也就是 Buffer - 图2位的数据

Buffer 的特性

Buffer 类的实例类似于 0 到 255 之间的整型数组(其他整数会通过 & 255 操作强制转换到此范围),Buffer 是一个 JavaScript 和 C++ 结合的模块,对象内存不经 V8 分配,而是由 C++ 申请、JavaScript 分配。缓冲区的大小在创建时确定,不能调整。

Buffer 对象实在过于常用,被直接内置到全局变量中,使用时候无需 require 引入

实例化 Buffer

在 Node.js v6 之前都是通过调用构造函数的方式实例化 Buffer,根据参数返回不同结果。
处于安全性原因,这种方式在 v6 后的版本中已经被废除,现在提供了三个职责清晰的函数处理实例化 Buffer 的工作

Buffer.from

Buffer.from 支持四种参数类型

  • Buffer.from(string [, encoding]):返回一个包含给定字符串的 Buffer,默认编码utf-8
  • Buffer.from(buffer):返回给定 Buffer 的一个副本 Buffer
  • Buffer.from(array):返回一个内容包含所提供的字节副本的 Buffer,数组中每一项是一个表示八位字节的数字,所以值必须在 0 ~ 255 之间,否则会取模
  • Buffer.from(arrayBuffer):返回一个与给定的 ArrayBuffer 共享内存的新 Buffer
  • Buffer.from(object[, offsetOrEncoding[, length]]):取 object 的 valueOf 或 Symbol.toPrimitive 初始化 Buffer ```javascript // 字符串 const str = ‘Hello’ const buf = Buffer.from(str) //

// buffer Buffer.from(buf) //

// 数组 const arr = [0, -1, 1, 255, 256] // 超过 255 会取模 Buffer.from(arr) //

// ArrayBuffer const arrayBuffer = new Uint8Array(4) // arrayBuffer = [ 0, 255, 255, 0 ] arrayBuffer[0] = 0 arrayBuffer[1] = -1 arrayBuffer[2] = 255 arrayBuffer[3] = 256 // 共享内存 const buf2 = Buffer.from(arrayBuffer.buffer) // arrayBuffer[0] = 5 buf2 //

// 对象 Buffer.from(new String(‘this is a test’)) //

  1. <a name="A8RGp"></a>
  2. ## Buffer.alloc
  3. `Buffer.alloc(size, [, fill, [, encoding]])`
  4. - 分配一个大小为 size 字节的新 Buffer,如果 fill 为 undefined,则用 0 填充 Buffer
  5. - size <integer> 新 Buffer 的所需长度
  6. - fill <string> | <Buffer> | <Uint8Array> | <integer> 用于预填充新 Buffer 的值。默认值: 0
  7. - encoding <string> 如果 fill 是一个字符串,则这是它的字符编码。默认值: `utf8`
  8. ```javascript
  9. const buf = Buffer.alloc(5);
  10. console.log(buf); // <Buffer 00 00 00 00 00>

如果指定了 fill,则分配的 Buffer 通过调用 buf.fill(fill) 进行初始化。

  1. const buf = Buffer.alloc(5, 'a');
  2. console.log(buf); // <Buffer 61 61 61 61 61>

Buffer.allocUnsafe

Buffer.allocUnsafe(size):分配一个大小为 size 字节的新 Buffer,allocUnsafe 执行速度比 alloc 快,但方法的名字听起来很不安全,确实也不安全

  1. const buf = Buffer.allocUnsafe(10);
  2. console.log(buf); // 打印内容不确定

Buffer 模块会预分配一个内部的大小为 Buffer.poolSize 的 Buffer 实例,作为快速分配的内存池(Buffer 池),可以用于使用 Buffer.allocUnsafe() 创建新的 Buffer 实例

Buffer.alloc() 永远不会使用内部的 Buffer 池。
Buffer.allocUnsafe(size)size <= Buffer.poolSize / 2 时将会使用内部的 Buffer 池 :::warning 当调用 Buffer.allocUnsafe() 时分配的内存段尚未初始化(不归零),这样分配内存速度很块,但分配到的内存片段可能包含旧数据。如果在使用的时候不覆盖这些旧数据就可能造成内存泄露,虽然速度快,尽量避免使用 :::

Buffer.allocUnsafeSlow

创建一个大小为 size 字节的新 Buffer,直接通过 c++ 进行内存分配,不会进行旧值填充,当分配的空间小于 4KB的时候,allocUnsafe 会直接使用预分配的 Buffer,因此速度比 allocUnsafeSlow 要快,当大于等于4KB的时候二者速度相差无异

编码

Buffer 目前支持以下几种编码格式

  • ascii
  • utf8
  • utf16le
  • base64
  • binary
  • hex

Buffer 和 String 转换

Buffer.from(string [, encoding]) 字符串转为 Buffer
buffer.toString([encoding[, start[, end]]]) Buffer 实例的 toString 方法可以将 Buffer 转为字符串

  1. const buf = Buffer.from('test', 'utf-8') // <Buffer 74 65 73 74>
  2. console.log(buf.toString()); // test
  3. console.log(buf.toString('hex')); // 74657374

Buffer 拼接

Buffer.concat(list[, totalLength]) 方法可以把多个 Buffer 实例拼接为一个 Buffer 实例

  1. const buf1 = Buffer.from('a');
  2. const buf2 = Buffer.from('b');
  3. const buf3 = Buffer.from('c');
  4. const buf = Buffer.concat([buf1, buf2, buf3]);
  5. console.log(buf); // <Buffer 61 62 63>

StringDecoder

乱码

在 NodeJS 中一个汉字由三个字节表示,如果我们处理中文字符的时候使用了不是 3 的倍数的字节数就会造成字符拼接乱码问题

  1. const buf = Buffer.from('中文字符串!');
  2. let gap = 5
  3. for (let i = 0; i < buf.length; i += gap) {
  4. var b = Buffer.allocUnsafe(gap);
  5. buf.copy(b, 0, i);
  6. console.log(b.toString());
  7. }

当 gap 为 5 时 当 gao 为 3 时
image.png image.png

解决方式

使用 string_decoder 模块可以解决这个问题

  1. const { StringDecoder } = require('string_decoder')
  2. const decoder = new StringDecoder('utf8')
  3. const buf = Buffer.from('中文字符串!')
  4. let gap = 5
  5. for (let i = 0; i < buf.length; i += gap) {
  6. var b = Buffer.allocUnsafe(gap);
  7. buf.copy(b, 0, i);
  8. console.log(decoder.write(b));
  9. }

:::info StringDecoder 在得到编码后,知道宽字节在 utf-8 下占 3 个字节,所以在处理末尾不全的字节时,会保留到第二次 write()。目前只能处理 UTF-8、Base64 和 UCS-2/UTF-16LE。 :::

Buffer 的其他常用 API

还有一些 Buffer 常用的 API,比较简单不用代码示例了

  • Buffer.isBuffer:判断对象是否为 Buffer
  • Buffer.isEncoding:判断 Buffer 对象编码
  • buf.length:返回 内存为此 Buffer 实例所申请的字节数,并不是 Buffer 实例内容的字节数
  • buf.indexOf:和数组的 indexOf 类似,返回某字符串、acsii 码或者 buf 在改 buf 中的位置
  • buf.copy:将一个 buf 的(部分)内容复制到另外一个 buf 中

了解了这些内容就可以进入 stream 的学习了