Node.js Buffer
Node.js Buffer 是二进制缓冲区概念在 Node.js 中的具体类型与 API;本页聚焦分配、编码、拼接、切片和数值读写语义。
#concept
#growing
#backend
#nodejs
#javascript
[!info] related notes
- 所属 MOC: Node.js MOC
- 上位概念: Buffer(二进制缓冲区)
- 流式处理: Node.js Stream
Node.js Buffer
一句话定义
Buffer 是 Node.js 提供的 Uint8Array 子类,用来处理字节数据,并额外提供编码转换、数值读写、拼接等 Node.js API。
角色边界
Buffer(二进制缓冲区) 维护“有限字节区域”的通用心智模型;本页只维护 Node.js 的具体实现语义。因此“Buffer 和 Stream 为什么是不同抽象”优先回到上位概念,本页重点回答:
- Node.js 怎样创建字节存储;
- 字符串怎样按指定编码转成字节、再解码回来;
slice()/subarray()的共享存储意味着什么;allocUnsafe()为什么要求调用方确保相关字节被覆盖;- 多字节整数怎样受大小端约束。
Buffer vs Uint8Array
| 特性 | Buffer | Uint8Array |
|---|---|---|
| 继承关系 | Buffer 继承自 Uint8Array | TypedArray 原生类型 |
| 底层存储 | 使用 ArrayBuffer backing store,并由 Node/V8 跟踪外部内存占用 | 使用 ArrayBuffer backing store |
| 编码支持 | 内置字符串编码转换 | 本身不提供 Node Buffer 的编码便利 API |
| 使用重点 | Node I/O 与二进制协议处理 | Web/ECMAScript 通用 typed-array 视图 |
| API | 丰富(read/write 系列) | 标准 TypedArray |
Buffer 是 Uint8Array 的子类,可以当 Uint8Array 使用,但有额外的编码和便利方法。
创建 Buffer
// 推荐:指定大小分配并清零
const buf = Buffer.alloc(10); // 10 字节,全部填充 0
const bufUnsafe = Buffer.allocUnsafe(10); // 10 字节,可能包含旧数据,使用前必须覆盖
// 从字符串创建
const bufStr = Buffer.from('hello', 'utf8');
const bufBase64 = Buffer.from('aGVsbG8=', 'base64');
const bufHex = Buffer.from('68656c6c6f', 'hex');
// 从数组/ArrayBuffer 创建
const bufArr = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]);
const bufAb = Buffer.from(new ArrayBuffer(10));
编码支持
Buffer 支持以下编码:utf8、ascii、latin1、base64、base64url、hex、utf16le
const buf = Buffer.from('你好', 'utf8');
console.log(buf.length); // 6(此例两个字符各占 3 个 UTF-8 字节)
console.log(buf.toString('hex')); // e4bda0e5a5bd
console.log(buf.toString('base64')); // 5L2g5aW9
// 指定起始偏移与结束偏移(不包含结束位置)解码
const partial = buf.toString('utf8', 0, 3); // '你'
拼接 Buffer
// concat:合并多个 Buffer
const buf1 = Buffer.from('Hello');
const buf2 = Buffer.from(' ');
const buf3 = Buffer.from('World');
const merged = Buffer.concat([buf1, buf2, buf3]);
console.log(merged.toString()); // 'Hello World'
// 指定总长度
const mergedWithLen = Buffer.concat([buf1, buf2, buf3], 11);
slice vs subarray
const buf = Buffer.from('Hello World');
// slice:返回视图(共享内存!修改子切片会影响原 Buffer)
const sliced = buf.slice(0, 5);
sliced[0] = 0x68; // 原 buf 变成 'hello World'
// subarray:和 slice 行为一致,也是共享内存
const sub = buf.subarray(0, 5);
// 如果需要独立副本,手动复制
const copy = Buffer.from(buf.subarray(0, 5));
关键区别:
slice和subarray都返回共享内存的视图- 修改视图会影响原始 Buffer
- 需要独立副本时用
Buffer.from(view)创建
读写二进制数据
const buf = Buffer.alloc(11);
// 写入不同类型的数值
buf.writeUInt8(0xFF, 0); // 无符号 8 位
buf.writeUInt16BE(0x1234, 1); // 大端序 16 位
buf.writeUInt32LE(0x12345678, 3); // 小端序 32 位
buf.writeFloatBE(3.14, 7); // 浮点数
// 读取
console.log(buf.readUInt8(0)); // 255
console.log(buf.readUInt16BE(1)); // 4660
console.log(buf.readUInt32LE(3)); // 305419896
内存注意事项
// allocUnsafe 可能包含旧数据(来自已释放的内存)
// 在读取、发送或暴露这些字节前,确保全部覆盖
const fast = Buffer.allocUnsafe(1024 * 1024);
fast.fill(0); // 手动清零
// 字节存储计入外部内存;Buffer 包装对象仍由 V8 管理
// --max-old-space-size 不是整个进程或 Buffer 字节存储的内存上限
console.log(Buffer.poolSize); // 默认 8192 字节
常见误区
allocUnsafe的“unsafe”指返回的内存未初始化;只有在后续逻辑确保相关字节全部被覆盖时才适合使用。slice/subarray返回共享底层存储的视图,不是独立副本。- 大端 vs 小端必须服从具体协议或文件格式,不能仅凭 CPU 架构猜测外部数据格式。
- 字符串编码必须在字节与文本两侧保持一致;同一组字节用不同编码解码会得到不同结果。
Buffer是Uint8Array的 Node.js 扩展,不应把Uint8Array当成本页别名。
学习检查
- 为什么 Node.js
Buffer可以传给期待Uint8Array的许多 API?反方向是否总成立? Buffer.from(view)与buf.subarray()在“复制还是共享底层存储”上有什么差异?- 什么条件下使用
allocUnsafe()才不会把旧内存内容暴露到输出? - 为什么协议字段必须明确 BE/LE,而不是依赖当前机器的 CPU 端序?
- 如果你只是想解释 Buffer 的通用概念,为什么应该先读 Buffer(二进制缓冲区) 而不是本页?