Node.js Buffer 与编码:二进制与字符集
HTTP 头、文件读写、加密、网络协议——最后都会落到 字节序列。在 JavaScript 里字符串是 UTF-16,字节则主要靠 Buffer(以及 TypedArray)来表达。
这一篇想讲清楚几件事:Buffer 和字符串怎么互转、编码参数什么时候不能省略、以及和 Stream 拼在一起时容易在哪一步混单位。
Buffer 是什么:固定长度的字节序列
Buffer 是 Uint8Array 的子类(在较新 Node 里对齐得更紧),表示一段 可变的二进制数据:
- 下标访问是 0–255 的字节;
- 可以和 字符串、TypedArray、ArrayBuffer 互相转换或 共享底层内存(
Buffer.from的拷贝行为要看重载)。
和「普通 JS 字符串」的分工可以记:
- 字符串:给人看的、带字符集语义的文本;
- Buffer:给协议、磁盘、socket 看的 原始字节。
字符串 ↔ Buffer:编码必须心里有数
常见 API:
Buffer.from(str, encoding):字符串 → Buffer;buf.toString(encoding):Buffer → 字符串。
encoding 省略时往往默认 utf8,但:
- 处理 非 UTF-8 来源(老系统、部分二进制协议伪装成文本)时,乱码或截断 会悄悄发生;
base64/hex常用于 可打印表示,不是「另一种文本编码」那么简单。
实践:
- 边界上(读文件、解包网络帧)明确写 encoding 或明确当 二进制 处理;
- 日志里打印 Buffer 前先
toString或截断,避免巨量十六进制刷屏。
切片与拷贝:subarray、引用与 Buffer.alloc
buffer.subarray(start, end):共享同一块底层存储,改切片会影响原 Buffer(要小心);Buffer.from某些重载会 拷贝,有的会 包装现有内存——以文档为准;- 新建 Buffer 优先
Buffer.alloc(清零),避免Buffer.allocUnsafe泄漏旧内存内容(除非性能敏感且立即覆写)。
安全敏感场景(密钥、token):
- 用完后 覆写或交给 GC,不要长期挂在全局对象上。
与 Stream:Readable 里流出来的是什么
data 事件 默认可能是 Buffer(除非设了编码把流变成字符串模式)。
含义:
- 按字节处理(定长协议)时保持 Buffer 管道;
- 按行处理文本 时可以在合适层
setEncoding('utf8')或自己 按 Buffer 解码。
和 Stream 篇合起来看:
- 背压 管的是「流不爆」;编码 管的是「字符不切半」——两个问题都要有人管。
TypedArray、DataView 与 WASM
若你在做:
- 二进制协议解析、与 WASM 交互、与原生 addon 交换内存,
会更多碰到 ArrayBuffer / DataView / 具体 TypedArray。和 Buffer 的关系可以粗记:
- Buffer 是 Node 里最好用的字节容器;
- TypedArray 更贴近 ECMAScript 标准 与 浏览器 侧代码。
选型:Node 内部优先 Buffer;要 跨环境复用算法 时再往 TypedArray 靠。
小结:字节不是「另一种字符串」
- Buffer 表示字节;字符串表示 已解码的文本(在已知编码前提下);
- 互转必带编码意识,默认 UTF-8 不是万能;
- 切片共享内存,协议解析时注意别名与拷贝;
- 和 Stream 搭配时,分清 二进制模式 与 字符串模式。
把单位搞清楚,后面看 crypto、压缩、序列化格式,会少一半「为什么多了个乱码」的调试时间。