Buffer 缓冲区
本教程共 76 篇 · 第 20 篇 · 更新于 2026-07-25 · 约 5 分钟阅读
20. Buffer 缓冲区
本节目标:Buffer 是什么、如何创建和操作二进制数据、编码转换。
JavaScript 字符串是 Unicode 字符序列,底层并不直接暴露字节。但文件系统、网络套接字、加密算法,打交道的基本单位都是二进制字节。Buffer 就是 Node.js 给 JavaScript 补上的一块「二进制操作面板」。
20.1 Buffer 是什么
Buffer 是一个类数组对象,每个元素是一个 0-255 的整数,对应一个字节。它不在 V8 的堆内存里分配,而是直接在 C++ 层面申请的原生内存,所以处理大段二进制数据时效率高、垃圾回收压力小。
// Buffer 是全局对象,不用 import
const buf = Buffer.from('Hello');
console.log(buf); // <Buffer 48 65 6c 6c 6f>
console.log(buf.length); // 5
Buffer.from() 是最安全的创建方式,从字符串、数组、另一个 Buffer 都能转。
20.2 创建 Buffer
安全分配:Buffer.alloc
// 申请 10 字节内存,全部初始化为 0
const buf = Buffer.alloc(10);
console.log(buf); // <Buffer 00 00 00 00 00 00 00 00 00 00>
Buffer.alloc 比旧的 new Buffer(size) 安全——后者不会清零内存,可能泄露敏感数据。v6 以后 new Buffer 就被废弃了,见到老代码里有,顺手改掉。
快速但不安全:Buffer.allocUnsafe
const buf = Buffer.allocUnsafe(1024);
// buf 里可能是内存里残留的任意数据
速度比 alloc 快一点,因为跳过了清零步骤。但如果你要立刻往里面写满自己的数据,用它也无所谓;如果创建后要外传,必须先填充。
从已有数据创建
// 从字符串
Buffer.from('你好', 'utf8');
// 从数组
Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
// 从 base64 字符串解码
Buffer.from('SGVsbG8=', 'base64'); // <Buffer 48 65 6c 6c 6f>
20.3 编码转换
Buffer 和字符串之间的转换靠编码规则。Node.js 支持的编码:
| 编码 | 说明 |
|---|---|
utf8 | 默认,变长 Unicode |
ascii | 仅 0-127 |
base64 | 常用于图片、数据 URL |
hex | 每字节转两个十六进制字符 |
latin1 / binary | 单字节扩展 ASCII |
ucs2 / utf16le | 双字节小端序 |
const buf = Buffer.from('Node.js');
console.log(buf.toString('hex')); // 4e6f64652e6a73
console.log(buf.toString('base64')); // Tm9kZS5qcw==
// 从 hex 还原
Buffer.from('4e6f64652e6a73', 'hex').toString(); // 'Node.js'
20.4 读写 Buffer
按索引访问
const buf = Buffer.from('ABC');
console.log(buf[0]); // 65,ASCII 码
console.log(String.fromCharCode(buf[0])); // 'A'
buf[0] = 97; // 改成 'a'
console.log(buf.toString()); // 'aBC'
Buffer 的长度固定,不能 push 或 pop。要扩展只能创建新的 Buffer。
切片 slice
const buf = Buffer.from('Hello, World!');
const slice = buf.slice(7, 12);
console.log(slice.toString()); // 'World'
// 注意:slice 不是深拷贝,修改 slice 会影响原 Buffer
slice[0] = 0x77; // 'w'
console.log(buf.toString()); // 'Hello, world!'
拷贝 copy
const src = Buffer.from('Hello');
const dst = Buffer.alloc(5);
src.copy(dst);
console.log(dst.toString()); // 'Hello'
需要深拷贝时,也可以用 Buffer.from(src) 或者 structuredClone。
查找与比较
const buf = Buffer.from('The quick brown fox');
console.log(buf.indexOf('brown')); // 10
console.log(buf.includes('fox')); // true
const a = Buffer.from('A');
const b = Buffer.from('B');
console.log(Buffer.compare(a, b)); // -1,a < b
20.5 Buffer 合并
const head = Buffer.from('Hello, ');
const tail = Buffer.from('World!');
const combined = Buffer.concat([head, tail]);
console.log(combined.toString()); // 'Hello, World!'
Buffer.concat 会返回一个新 Buffer。如果预先知道总长度,可以传第二个参数避免二次分配:
Buffer.concat([head, tail], head.length + tail.length);
20.6 与 TypedArray 互操作
Buffer 是 Uint8Array 的子类,所以 TypedArray 的 API 都能用:
const buf = Buffer.from([1, 2, 3, 4]);
// 用 DataView 读取多字节整数
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
console.log(view.getUint16(0)); // 258,大端序 0x0102
// 转成其他 TypedArray
const ints = new Uint32Array(buf.buffer, buf.byteOffset, 1);
但要注意 buf.buffer 可能指向一个更大的底层 ArrayBuffer。如果需要精确控制,用 buf.subarray 或 Buffer.from 再包一层。
20.7 实战:处理 Base64 图片上传
Web 上传图片时,前端常把文件转成 Base64 字符串发来。后端需要解码成 Buffer,再写入文件系统:
import { writeFile } from 'node:fs/promises';
// 前端传来的数据:data:image/png;base64,iVBORw0KGgo...
const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==';
const base64 = dataUrl.split(',')[1];
const buf = Buffer.from(base64, 'base64');
await writeFile('upload.png', buf);
console.log(`保存成功,大小 ${buf.length} 字节`);
反过来,把图片转成 Base64 给前端:
import { readFile } from 'node:fs/promises';
const buf = await readFile('upload.png');
const base64 = buf.toString('base64');
const dataUrl = `data:image/png;base64,${base64}`;
20.8 性能与安全提示
Buffer.allocUnsafe创建后若未立即写入,可能泄露旧内存数据。处理密钥、Token 时尤其要小心。- 含敏感信息的 Buffer,用完可以手动
buf.fill(0)清零。 - 大文件别一次性读进 Buffer,用 Stream(下一章讲)分段处理。