Module Buffer

Buffer

Buffer 对象用于表示固定长度字节序列。许多 Node.js 的 API 支持 Buffers。

Buffer 类是 JavaScript 的 Uint8Array 类的子类,并通过方法扩展了额外的用例。Node.js 的 API 在支持 Buffer 的地方也接受普通的 Uint8Arrays。

虽然 Buffer 类在全局范围内可用,但仍建议通过 import 或 require 语句明确引用它。

Node.js的Buffer接受接收到的编码字符串的所有大小写变化。例如,UTF-8可以指定为’utf8’,‘UTF8’或’uTf8’。

import { Buffer } from 'node:buffer';

// Creates a zero-filled Buffer of length 10.
const buf1 = Buffer.alloc(10);

// Creates a Buffer of length 10,
// filled with bytes which all have the value `1`.
const buf2 = Buffer.alloc(10, 1);

// Creates an uninitialized buffer of length 10.
// This is faster than calling Buffer.alloc() but the returned
// Buffer instance might contain old data that needs to be
// overwritten using fill(), write(), or other functions that fill the Buffer's
// contents.
const buf3 = Buffer.allocUnsafe(10);

// Creates a Buffer containing the bytes [1, 2, 3].
const buf4 = Buffer.from([1, 2, 3]);

// Creates a Buffer containing the bytes [1, 1, 1, 1] – the entries
// are all truncated using `(value & 255)` to fit into the range 0–255.
const buf5 = Buffer.from([257, 257.5, -255, '1']);

// Creates a Buffer containing the UTF-8-encoded bytes for the string 'tést':
// [0x74, 0xc3, 0xa9, 0x73, 0x74] (in hexadecimal notation)
// [116, 195, 169, 115, 116] (in decimal notation)
const buf6 = Buffer.from('tést');

// Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74].
const buf7 = Buffer.from('tést', 'latin1');

在进行Buffer和字符串之间的转换时,可以指定字符编码。如果没有指定字符编码,将使用UTF-8作为默认编码。这意味着如果您不明确指定字符编码,Node.js将默认使用UTF-8来编码和解码数据。

import { Buffer } from 'node:buffer';

const buf = Buffer.from('hello world', 'utf8');

console.log(buf.toString('hex'));
// Prints: 68656c6c6f20776f726c64
console.log(buf.toString('base64'));
// Prints: aGVsbG8gd29ybGQ=

console.log(Buffer.from('fhqwhgads', 'utf8'));
// Prints: <Buffer 66 68 71 77 68 67 61 64 73>
console.log(Buffer.from('fhqwhgads', 'utf16le'));
// Prints: <Buffer 66 00 68 00 71 00 77 00 68 00 67 00 61 00 64 00 73 00>

当前Node.js支持的字符编码包括以下几种:

  1. ‘utf8’(别名:‘utf-8’):多字节编码的Unicode字符。许多网页和其他文档格式使用UTF-8。这是默认的字符编码。当将Buffer解码为字符串时,如果字符串中包含的数据不是有效的UTF-8数据,将使用Unicode替换字符U+FFFD 来表示这些错误。

  2. ‘utf16le’(别名:‘utf-16le’):多字节编码的Unicode字符。与’utf8’不同,字符串中的每个字符将使用2或4个字节进行编码。Node.js仅支持UTF-16的小端(little-endian)变体。

  3. ’latin1’:Latin-1代表ISO-8859-1。这种字符编码仅支持Unicode字符从U+0000到U+00FF。每个字符使用一个字节进行编码。不在该范围内的字符将被截断,并映射到该范围内的字符。

将Buffer转换为字符串使用上述字符编码之一称为解码(decoding),而将字符串转换为Buffer称为编码(encoding)。

Node.js还支持以下二进制到文本编码。对于二进制到文本编码,命名约定是相反的:将Buffer转换为字符串通常被称为编码(encoding),将字符串转换为Buffer被称为解码(decoding)。

  1. ‘base64’:Base64编码。当从字符串创建Buffer时,此编码还将正确接受RFC 4648第5节中指定的“URL和文件名安全字母”。在base64编码的字符串中包含的空白字符,如空格、制表符和换行符,将被忽略。

  2. ‘base64url’:base64url编码,按照RFC 4648第5节中指定的方式进行编码。当从字符串创建Buffer时,此编码还将正确接受常规的base64编码字符串。将Buffer编码为字符串时,此编码将省略填充。

  3. ‘hex’:将每个字节编码为两个十六进制字符。当解码不仅包含偶数个十六进制字符的字符串时,可能会发生数据截断。下面是一个示例。

此外,还支持以下传统字符编码:

  1. ‘ascii’:仅用于7位ASCII数据。将字符串编码为Buffer时,这等效于使用’latin1’。将Buffer解码为字符串时,使用此编码将在解码为’latin1’之前额外取消设置每个字节的最高位。一般来说,在编码或解码ASCII文本时,应该选择使用’utf8’(如果数据始终为ASCII-only,则选择’latin1’)更好。此编码仅用于传统兼容性。

  2. ‘binary’:’latin1’的别名。此编码的名称可能会非常误导,因为这里列出的所有编码都在字符串和二进制数据之间进行转换。通常,对于字符串和Buffer之间的转换,‘utf8’是正确的选择。

  3. ‘ucs2’,‘ucs-2’:‘utf16le’的别名。UCS-2曾指的是不支持代码点大于U+FFFF的UTF-16变体。在Node.js中,始终支持这些代码点。

import { Buffer } from 'node:buffer';

Buffer.from('1ag123', 'hex');
// Prints <Buffer 1a>, data truncated when first non-hexadecimal value
// ('g') encountered.

Buffer.from('1a7', 'hex');
// Prints <Buffer 1a>, data truncated when data ends in single digit ('7').

Buffer.from('1634', 'hex');
// Prints <Buffer 16 34>, all data represented.

现代Web浏览器遵循WHATWG编码标准,该标准将’latin1’和’ISO-8859-1’都别名为’win-1252’。这意味着在执行诸如http.get()之类的操作时,如果返回的字符集是WHATWG规范中列出的字符集之一,那么服务器实际上可能返回了’win-1252’编码的数据,使用’latin1’编码可能会不正确地解码字符。

换句话说,根据WHATWG标准,’latin1’和’ISO-8859-1’实际上被视为’win-1252’编码。因此,如果您处理来自Web服务器的数据,并且该数据的字符集在WHATWG规范中列出,最好使用’win-1252’编码而不是’latin1’,以确保正确解码字符。

Buffers and TypedArrays

Buffer 实例也是 JavaScript 的 Uint8Array 和 TypedArray 实例。所有 TypedArray 的方法都可以在 Buffer 上使用。然而,Buffer API 和 TypedArray API 之间存在微妙的不兼容(subtle incompatibilities)之处。

特别是:

  • 虽然 TypedArray.prototype.slice() 创建了 TypedArray 的一部分的副本,但 Buffer.prototype.slice() 创建了一个视图,而不进行复制。这种行为可能会令人惊讶,它只为了保持向后兼容而存在。TypedArray.prototype.subarray() 可以用于在 Buffers 和其他 TypedArrays 上实现与 Buffer.prototype.slice() 相同的行为,并且应该更受欢迎。

  • buf.toString() 与其 TypedArray 等效版本不兼容。

  • 许多方法,例如 buf.indexOf(),支持额外的参数。

有两种方法可以从 Buffer 创建新的 TypedArray 实例:

  • 将 Buffer 传递给 TypedArray 构造函数将复制 Buffer 的内容,将其解释为整数数组,而不是目标类型的字节序列。
import { Buffer } from 'node:buffer';

const buf = Buffer.from([1, 2, 3, 4]);
const uint32array = new Uint32Array(buf);

console.log(uint32array);

// Prints: Uint32Array(4) [ 1, 2, 3, 4 ]
  • 通过传递 Buffer 的底层 ArrayBuffer 来创建一个与 Buffer 共享内存的 TypedArray。
import { Buffer } from 'node:buffer';

const buf = Buffer.from('hello', 'utf16le');
const uint16array = new Uint16Array(
  buf.buffer,
  buf.byteOffset,
  buf.length / Uint16Array.BYTES_PER_ELEMENT);

console.log(uint16array);

// Prints: Uint16Array(5) [ 104, 101, 108, 108, 111 ]

通过使用 TypedArray 对象的 .buffer 属性,也可以以相同的方式创建一个与 TypedArray 实例共享相同分配的内存的新 Buffer。在这个上下文中,Buffer.from() 的行为类似于 new Uint8Array()。

import { Buffer } from 'node:buffer';

const arr = new Uint16Array(2);

arr[0] = 5000;
arr[1] = 4000;

// Copies the contents of `arr`.
const buf1 = Buffer.from(arr);

// Shares memory with `arr`.
const buf2 = Buffer.from(arr.buffer);

console.log(buf1);
// Prints: <Buffer 88 a0>
console.log(buf2);
// Prints: <Buffer 88 13 a0 0f>

arr[1] = 6000;

console.log(buf1);
// Prints: <Buffer 88 a0>
console.log(buf2);
// Prints: <Buffer 88 13 70 17>

当使用 TypedArray 的 .buffer 创建一个 Buffer 时,可以通过传递 byteOffset 和 length 参数来仅使用底层 ArrayBuffer 的一部分。

import { Buffer } from 'node:buffer';

const arr = new Uint16Array(20);
const buf = Buffer.from(arr.buffer, 0, 16);

console.log(buf.length);
// Prints: 16

Buffer.from() 和 TypedArray.from() 具有不同的签名和实现。具体来说,TypedArray 变体接受第二个参数,这是一个在调用类型化数组的每个元素上调用的映射函数:

TypedArray.from(source[, mapFn[, thisArg]])

然而,Buffer.from() 方法不支持使用映射函数:

Buffer.from(array)
Buffer.from(buffer)
Buffer.from(arrayBuffer[, byteOffset[, length]])
Buffer.from(string[, encoding])

Buffers and iteration

Buffer 实例可以使用 for..of 语法进行迭代:

import { Buffer } from 'node:buffer';

const buf = Buffer.from([1, 2, 3]);

for (const b of buf) {
  console.log(b);
}
// Prints:
//   1
//   2
//   3

此外,还可以使用 buf.values()、buf.keys() 和 buf.entries() 方法来创建迭代器:

const buf = Buffer.from([1, 2, 3, 4]);

// 使用 buf.values() 创建值迭代器
const values = buf.values();
for (const value of values) {
  console.log(value);
}

// 使用 buf.keys() 创建键迭代器
const keys = buf.keys();
for (const key of keys) {
  console.log(key);
}

// 使用 buf.entries() 创建条目迭代器
const entries = buf.entries();
for (const entry of entries) {
  console.log(entry);
}

Class: Blob

Blob(二进制大对象)是一个封装了不可变的原始数据的对象,可以安全地在多个工作线程之间共享。

new buffer.Blob([sources[, options]])

创建一个新的 Blob 对象,其中包含给定 sources 的连接。

  • sources:一个包含字符串、ArrayBuffer、TypedArray、DataView 或 Blob 对象的数组,或者这些对象的混合,将存储在 Blob 中。
  • options:一个对象,可包括以下属性:
    • endings:一个字符串,可以是’transparent’或’native’。当设置为’native’时,字符串源部分中的换行符将被转换为平台本地的换行符,如 require(’node:os’).EOL 所指定。
    • type:Blob 的内容类型。type 的意图是传达数据的 MIME 媒体类型,但不执行类型格式的验证。
import { Blob } from 'node:buffer';

const blob = new Blob(['hello there']);

// 示例中的代码展示如何创建 Blob 对象以及如何将其发送到不同的 MessagePort。

Blob 对象有以下方法和属性:

  • blob.arrayBuffer(): 返回一个 Promise,该 Promise 在完成时包含 Blob 数据的 ArrayBuffer 的副本。
  • blob.size: Blob 的总字节大小。
  • blob.slice([start[, end[, type]]]): 创建并返回一个新的 Blob,其中包含此 Blob 对象数据的子集。原始 Blob 不会被修改。
  • blob.stream(): 返回一个新的 ReadableStream,允许读取 Blob 的内容。
  • blob.text(): 返回一个 Promise,该 Promise 在完成时包含 Blob 解码为 UTF-8 字符串的内容。
  • blob.type: Blob 的内容类型。

Blob 对象和 MessageChannel 结合使用时

一旦创建了一个 Blob 对象,它可以通过 MessagePort 发送到多个目标,而不会传输或立即复制数据。Blob 包含的数据只有在调用 arrayBuffer() 或 text() 方法时才会被复制。

这种行为非常有用,因为它允许在多个线程或进程之间有效地共享大量数据,而无需立即复制整个数据块。只有在接收端真正需要访问数据的副本时才会进行复制,这有助于减少不必要的内存开销和数据传输成本。这在多线程或多进程的应用程序中非常重要,因为它可以提高性能和效率。

import { Blob } from 'node:buffer';
import { setTimeout as delay } from 'node:timers/promises';

const blob = new Blob(['hello there']);

const mc1 = new MessageChannel();
const mc2 = new MessageChannel();

mc1.port1.onmessage = async ({ data }) => {
  console.log(await data.arrayBuffer());
  mc1.port1.close();
};

mc2.port1.onmessage = async ({ data }) => {
  await delay(1000);
  console.log(await data.arrayBuffer());
  mc2.port1.close();
};

mc1.port2.postMessage(blob);
mc2.port2.postMessage(blob);

// The Blob is still usable after posting.
blob.text().then(console.log);

Class: Buffer

Buffer 类是一个用于直接处理二进制数据的全局类型。它可以通过多种方式构建。

Static method:Buffer.alloc(size[, fill[, encoding]])

  • size :新 Buffer 的期望长度。
  • fill | | | :要用来预填充新 Buffer 的值。默认为 0。
  • encoding :如果 fill 是字符串,这是它的编码方式。默认为 ‘utf8’。

此方法分配一个新的大小为 size 字节的 Buffer。如果 fill 未定义,那么 Buffer 将被填充为零。

import { Buffer } from 'node:buffer';

const buf = Buffer.alloc(5);

console.log(buf);
// Prints: <Buffer 00 00 00 00 00>

如果 size 大于 buffer.constants.MAX_LENGTH 或小于 0,将会抛出 ERR_OUT_OF_RANGE 错误。

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

import { Buffer } from 'node:buffer';

const buf = Buffer.alloc(5, 'a');

console.log(buf);
// Prints: <Buffer 61 61 61 61 61>

如果同时指定了 fill 和 encoding,那么分配的 Buffer 将通过调用 buf.fill(fill, encoding) 进行初始化。

import { Buffer } from 'node:buffer';

const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');

console.log(buf);
// Prints: <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>

调用 Buffer.alloc() 可能比替代方法 Buffer.allocUnsafe() 慢一些,但它确保新创建的 Buffer 实例的内容不会包含来自先前分配的敏感数据,包括未分配给 Buffers 的数据。

如果 size 不是一个数字,将会抛出 TypeError。这意味着 size 必须是一个有效的整数,用于指定要创建的 Buffer 的大小。

Static method:Buffer.allocUnsafe(size)#

  • size :新 Buffer 的期望长度。
    分配一个新的大小为 size 字节的 Buffer。如果 size 大于 buffer.constants.MAX_LENGTH 或小于 0,将会抛出 ERR_OUT_OF_RANGE 错误。

通过这种方式创建的 Buffer 实例的底层内存未初始化。新创建的 Buffer 的内容是未知的,并且可能包含敏感数据。如果您需要初始化 Buffer 实例为零值,请使用 Buffer.alloc() 方法。Buffer.alloc() 方法会确保 Buffer 的内容被初始化为零,以避免潜在的数据泄漏问题。

import { Buffer } from 'node:buffer';

const buf = Buffer.allocUnsafe(10);

console.log(buf);
// Prints (contents may vary): <Buffer a0 8b 28 3f 01 00 00 00 50 32>

buf.fill(0);

console.log(buf);
// Prints: <Buffer 00 00 00 00 00 00 00 00 00 00>

如果 size 不是一个数字,将会抛出 TypeError。

Buffer 模块预分配了一个内部的 Buffer 实例,大小为 Buffer.poolSize,该实例用作池,用于快速分配新的 Buffer 实例。这些新的 Buffer 实例是使用 Buffer.allocUnsafe()Buffer.from(array)Buffer.concat() 创建的,但仅当 size 小于或等于 Buffer.poolSize >> 1(Buffer.poolSize 除以 2 的向下取整)时才会使用该池。

这个预分配的内部内存池的使用是调用 Buffer.alloc(size, fill)Buffer.allocUnsafe(size).fill(fill) 之间的关键区别。具体来说,Buffer.alloc(size, fill) 永远不会使用内部的 Buffer 池,而 Buffer.allocUnsafe(size).fill(fill) 会在 size 小于或等于 Buffer.poolSize 的一半时使用内部的 Buffer 池。这个区别微妙,但在应用程序需要 Buffer.allocUnsafe() 提供的额外性能时,它可能非常重要。使用内部池可以加速内存分配和释放,提高性能。

Static method: Buffer.allocUnsafeSlow(size)

  • size :新 Buffer 的期望长度。

分配一个新的大小为 size 字节的 Buffer。如果 size 大于 buffer.constants.MAX_LENGTH 或小于 0,将会抛出 ERR_OUT_OF_RANGE 错误。如果 size 为 0,则创建一个零长度的 Buffer。

通过这种方式创建的 Buffer 实例的底层内存未初始化。新创建的 Buffer 的内容是未知的,并且可能包含敏感数据。如果您需要初始化这样的 Buffer 实例为零值,可以使用 buf.fill(0) 来进行初始化。

当使用 Buffer.allocUnsafe() 分配新的 Buffer 实例时,如果分配的大小小于 4 KiB,那么这些小分配将从一个预先分配的单一 Buffer 中切片。这允许应用程序避免创建许多单独分配的 Buffer 实例所带来的垃圾收集开销。这种方法通过消除需要跟踪和清理许多单独的 ArrayBuffer 对象来提高性能和内存使用。

然而,在某些情况下,开发人员可能需要从池中保留一小块内存,而且时间不确定,这种情况下可能需要使用 Buffer.allocUnsafeSlow() 创建一个不使用的 Buffer 实例,然后复制相关的数据部分。这个方法可以让开发人员更精细地控制内存的使用。

import { Buffer } from 'node:buffer';

// Need to keep around a few small chunks of memory.
const store = [];

socket.on('readable', () => {
  let data;
  while (null !== (data = readable.read())) {
    // Allocate for retained data.
    const sb = Buffer.allocUnsafeSlow(10);

    // Copy the data into the new allocation.
    data.copy(sb, 0, 0, 10);

    store.push(sb);
  }
});

如果 size 不是一个数字,将会抛出 TypeError。

Static method: Buffer.byteLength(string[, encoding])