node - 内置模块 - buffer

101 阅读4分钟

Buffer 是 Node.js 中用于操作二进制数据的全局类,可以直接使用,无需导入。

它的主要作用是存储和处理二进制数据,类似于一个伪数组。

  • 每个 Buffer 实例的元素占用 1 个字节(8 位),并且通常以十六进制形式显示,方便查看和操作。
  • 在 Node.js 默认使用的 UTF-8 编码中:
    • 英文字母(以及其他 ASCII 字符)占用 1 个字节。
    • 汉字等非 ASCII 字符通常占用 3 个字节。
const buffer = new Buffer('hello');

// 字幕 => ASCII 码 => 二进制 => 十六进制
// H => 104 => 01101000 => 68
// e => 101 => 01100101 => 65
// l => 108 => 01101100 => 6C
// l => 108 => 01101100 => 6C
// o => 111 => 01101111 => 6F
console.log(buffer); // <Buffer 68 65 6c 6c 6f>

在旧版本的 Node.js 中,使用 new Buffer() 创建 Buffer 时,会分配一块未初始化的内存。这意味着内存中的旧数据可能会被暴露,存在潜在的安全风险(例如泄露敏感信息)

因此从 Node.js 6.0.0 开始,推荐使用 Buffer.from() 方法来替换new Buffer()。他们功能完全一致,但为了向后兼容, new Buffer()依旧是可用的

const buffer = Buffer.from('你好');

// 你 => e4 bd a0 => 11100100 10111101 10100000
// 好 => e5 a5 bd => 11100101 10100101 10111101
console.log(buffer); // <Buffer e4 bd a0 e5 a5 bd>

参数

Buffer.from可以传递的参数为 数组 或 字符串

const buffer = Buffer.from([1, 2, 3]);
console.log(buffer); // <Buffer 01 02 03>

const buffer2 = Buffer.from('hello');
console.log(buffer2); // <Buffer 68 65 6c 6c 6f>

分配内存

在早期版本的 Node.js(v6.x 及更早版本)中,new Buffer 是创建 Buffer 的主要方式:

  • new Buffer(size):分配一块指定大小的内存,但内容未初始化,可能包含旧数据(如敏感信息),存在信息泄露风险
  • new Buffer(data):根据传入的数据(如字符串、数组等)创建一个 Buffer

为此 Node.js 对该方法进行了拆分

  1. Buffer.alloc(size)

    • 分配一块指定大小的内存,并将内容初始化为零

    • 推荐使用,默认安全。

    • 示例:

      const buffer = Buffer.alloc(10);
      console.log(buffer); // <Buffer 00 00 00 00 00 00 00 00 00 00>
      
  2. Buffer.allocUnsafe(size)

    • 分配一块指定大小的内存,但内容未初始化

    • 性能更高,但需要开发者自行清理内存内容,谨慎使用。

    • 示例:

      const buffer = Buffer.allocUnsafe(10);
      console.log(buffer); // 输出未初始化的随机内容
      
  3. Buffer.from(data)

    • 根据传入的数据(如字符串、数组等)创建一个新的 Buffer

    • 替代 new Buffer(data) 的安全方法。

    • 示例:

      const buffer = Buffer.from('你好');
      console.log(buffer); // <Buffer e4 bd a0 e5 a5 bd>
      

目前依旧可以使用new Buffer('你好'),但是无法使用new Buffer(10)

使用new Buffer(10), 控制台会静默失效,并抛出经过⚠️

编码格式

Buffer 默认使用 UTF-8 编码来存储字符串。如果我们希望使用其他编码格式,可以通过 Buffer.from() 的第二个参数来指定

  • UTF-8:中文字符通常需要 3 个字节
  • UTF-16:中文字符通常需要 2 个字节
// utf-8 等价于 utf8
// utf16le 等价于 utf-16le 「 utf-16 的 小端字节序(little endian) 版本 」
const buffer = Buffer.from('你好', 'utf16le');
console.log(buffer); // <Buffer 60 4f 7d 59>

同样new Buffer也可以通过第二个参数,指定编码格式

const buffer = new Buffer('你好', 'utf-16le');
console.log(buffer); // <Buffer 60 4f 7d 59>

转字符串

我们可以将 Buffer 转换回字符串,使用 toString() 方法

const buffer = Buffer.from('hello');
console.log(buffer.toString()); // hello

通用toString可以通过参数指定转换的编码格式。在编码和解码过程中,必须保证使用的编码格式一致,以避免乱码问题

const buffer = Buffer.from('你好', 'utf16le');
console.log(buffer.toString('utf16le')); // 正确解码
console.log(buffer.toString('utf8'));    // 错误解码(可能出现乱码)

文件读取

import fs from 'fs/promises';

// 读取文件时,默认读取的是二进制数据 <Buffer 实例>
const data = await fs.readFile('./assets/users.json');
console.log(data);
// toString的默认编码是utf-8
console.log(data.toString()); // Buffer => String

// 读取文件时, 指定编码
const data2 = await fs.readFile('./assets/users.json', 'utf-8');
console.log(data2);

内存池

在 Node.js 中,每次创建 Buffer 时,如果直接向操作系统申请内存,会导致频繁的系统调用,从而影响性能。为了优化这种情况,Node.js 引入了内存池机制

当我们第一次创建一个小型 Buffer(比如 Buffer.alloc(8)),虽然只需要 8 字节,但 Node.js 实际上会一次性向操作系统申请一块**8KB(8192 字节)**的内存作为内存池。这块内存池可以被后续的小型 Buffer 重复使用。

  • 如果创建的 Buffer 大小小于 8KB,且内存池中有足够的空间,Node.js 会直接从内存池中分配内存,而不需要再次向操作系统申请。
  • 如果内存池的剩余空间不足,则会重新申请一块新的 8KB 内存。
  • 对于超过 8KB 的大 Buffer(比如 10KB),Node.js 不会使用内存池,而是直接向操作系统申请独立的内存。

内存池机制只适用于小型 Buffer(小于 8KB)。对于较大的 Buffer,无论使用 Buffer.alloc() 还是 Buffer.from(),都会直接向操作系统申请内存。