首页 / Node.js 教程 / Buffer 缓冲区

Node.js 教程

Buffer 缓冲区

本教程共 76 篇 · 第 20 篇 · 更新于 2026-07-25 · 约 5 分钟阅读

Node.jsBuffer二进制编码缓冲区

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 的长度固定,不能 pushpop。要扩展只能创建新的 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.subarrayBuffer.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(下一章讲)分段处理。