Node.js: Buffer 与二进制数据
最后更新:2026-08-26
Bob 在开发图片上传服务时,用户上传的 JPEG 文件经过 fs.readFile 读取后变成了一串乱码——JavaScript 的字符串以 UTF-16 编码存储,无法正确表示 0x00 到 0xFF 之间的所有字节值。他发现 Node.js 提供了一个专门处理二进制数据的全局对象 Buffer,它像一段原始内存,每个位置精确对应一个字节。从文件读写到网络传输,从图片处理到加密计算,Buffer 是 Node.js 处理二进制数据的核心工具。
1. 你将学到
- 使用
Buffer.alloc()/Buffer.from()/Buffer.concat()/buf.slice()创建和操作 Buffer - 理解编码与解码机制(utf8 / base64 / hex / ascii / binary)
- 实现 Buffer 与 String 之间的相互转换
- 使用
fs.readFile读取二进制文件并获得 Buffer - 使用
Buffer.byteLength/Buffer.isBuffer/buf.length/buf.toString()查询 Buffer 信息 - 理解 TypedArray 与 Buffer 的关系
- 了解 Buffer 在网络、文件和加密场景中的实际应用
2. Buffer 是什么
Buffer 是 Node.js 提供的全局对象,用于在 V8 堆内存之外分配一段固定大小的原始二进制数据区域。每个元素占用 1 字节(8 位),值范围为 0~255。Buffer 不需要 require,直接可用。
JAVASCRIPT
const buf = Buffer.alloc(4);
console.log(buf);
console.log(buf.length);
TEXT
📖 仅展示
<Buffer 00 00 00 00>
4
flowchart LR
subgraph Input["输入源"]
D[磁盘文件]
N[网络请求]
C[加密运算]
end
subgraph Core["Node.js 运行时"]
B[Buffer<br/>原始二进制数据]
S[String / JSON<br/>结构化数据]
end
D -->|二进制读取| B
N -->|原始字节| B
C -->|哈希/签名| B
B -->|toString / decode| S
S -->|Buffer.from / encode| B
B -->|二进制写入| D
B -->|原始字节| N
3. Buffer 创建方式
Node.js 提供了多种创建 Buffer 的方式,不同方式适用于不同场景。
| 方法 | 初始化内容 | 安全性 | 性能 | 适用场景 |
|---|---|---|---|---|
Buffer.alloc(size) |
填充 0 | 安全 | 较慢 | 需要干净的新 Buffer |
Buffer.allocUnsafe(size) |
未初始化(旧数据) | 不安全 | 较快 | 性能敏感且立即填充 |
Buffer.from(array) |
数组元素值 | 安全 | 一般 | 从字节数组创建 |
Buffer.from(string, encoding) |
编码后的字符串 | 安全 | 一般 | 从字符串创建 |
Buffer.from(buffer) |
复制源 Buffer | 安全 | 一般 | 克隆 Buffer |
▶ 示例:alloc vs allocUnsafe
JAVASCRIPT
const safe = Buffer.alloc(8);
console.log('alloc:', safe);
const unsafe = Buffer.allocUnsafe(8);
console.log('allocUnsafe:', unsafe);
TEXT
📖 仅展示
alloc: <Buffer 00 00 00 00 00 00 00 00>
allocUnsafe: <Buffer a0 3f 1b 00 00 00 00 00>
▶ 示例:从数组和字符串创建
JAVASCRIPT
const fromArr = Buffer.from([72, 101, 108, 108, 111]);
console.log('from array:', fromArr.toString());
const fromStr = Buffer.from('Hello', 'utf8');
console.log('from string:', fromStr.toString());
const fromHex = Buffer.from('48656c6c6f', 'hex');
console.log('from hex:', fromHex.toString());
TEXT
📖 仅展示
from array: Hello
from string: Hello
from hex: Hello
4. 编码与解码
Node.js 支持多种字符编码,Buffer 可以在不同编码间自由转换。
| 编码 | 说明 | 每字符字节数 | 典型用途 |
|---|---|---|---|
| utf8 | Unicode 可变长编码 | 1~4 | 文本处理(默认编码) |
| ascii | 7 位 ASCII | 1 | 纯英文文本 |
| base64 | Base64 编码 | 约 4/3 原始 | 图片内嵌、邮件附件 |
| hex | 十六进制表示 | 2 | 调试输出、哈希值展示 |
| binary / latin1 | 每字节直接映射字符 | 1 | 逐字节操作 |
▶ 示例:编码转换
JAVASCRIPT
const text = 'Node.js 缓冲区';
const utf8Buf = Buffer.from(text, 'utf8');
console.log('utf8 bytes:', utf8Buf.length);
const base64 = utf8Buf.toString('base64');
console.log('base64:', base64);
const hex = utf8Buf.toString('hex');
console.log('hex:', hex);
const decoded = Buffer.from(base64, 'base64').toString('utf8');
console.log('decoded:', decoded);
TEXT
📖 仅展示
utf8 bytes: 14
base64: Tm9kZS5qcyDliIbku6znqIvl
hex: 4e6f64652e6a7320e7bc93e586b2e58cba
decoded: Node.js 缓冲区
▶ 示例:base64 编码图片数据
JAVASCRIPT
const fs = require('fs');
const imgBuf = fs.readFileSync('logo.png');
const dataUri = 'data:image/png;base64,' + imgBuf.toString('base64');
console.log('Data URI length:', dataUri.length);
5. Buffer 与 String 互转
Buffer 和 String 之间的转换是日常开发中最常见的操作。
| 方向 | 方法 | 说明 |
|---|---|---|
| String → Buffer | Buffer.from(str, encoding) |
默认 encoding 为 utf8 |
| Buffer → String | buf.toString(encoding) |
默认 encoding 为 utf8 |
| 查询字符串字节长度 | Buffer.byteLength(str, encoding) |
返回字节而非字符数 |
▶ 示例:字符数 vs 字节数
JAVASCRIPT
const str = '你好世界';
console.log('字符数:', str.length);
console.log('字节数 (utf8):', Buffer.byteLength(str, 'utf8'));
console.log('字节数 (ascii):', Buffer.byteLength(str, 'ascii'));
const buf = Buffer.from(str, 'utf8');
console.log('buf.length:', buf.length);
console.log('还原字符串:', buf.toString('utf8'));
TEXT
📖 仅展示
字符数: 4
字节数 (utf8): 12
字节数 (ascii): 4
buf.length: 12
还原字符串: 你好世界
6. Buffer 操作方法
(1) Buffer 常用方法速查表
| 方法 | 用途 | 返回值 |
|---|---|---|
Buffer.alloc(size, fill) |
创建并填充 | 新 Buffer |
Buffer.from(source, enc) |
从源创建 | 新 Buffer |
Buffer.concat(list, totalLen) |
拼接多个 Buffer | 新 Buffer |
Buffer.isBuffer(obj) |
判断是否为 Buffer | boolean |
Buffer.byteLength(str, enc) |
字符串字节长度 | number |
buf.slice(start, end) |
截取视图(共享内存) | Buffer 视图 |
buf.subarray(start, end) |
同 slice | Buffer 视图 |
buf.toString(enc) |
转为字符串 | string |
buf.write(str, offset, enc) |
向 Buffer 写入字符串 | 写入字节数 |
buf.copy(target, tStart, sStart, sEnd) |
复制到目标 Buffer | 复制字节数 |
buf.equals(otherBuf) |
比较内容是否相同 | boolean |
buf.compare(otherBuf) |
字典序比较 | -1 / 0 / 1 |
buf.fill(value, start, end) |
填充指定范围 | 原 Buffer |
buf.indexOf(value, byteOffset) |
查找字节位置 | number |
▶ 示例:concat 拼接
JAVASCRIPT
const part1 = Buffer.from('Hello, ');
const part2 = Buffer.from('Buffer!');
const merged = Buffer.concat([part1, part2]);
console.log(merged.toString());
console.log('total length:', merged.length);
TEXT
📖 仅展示
Hello, Buffer!
total length: 14
▶ 示例:slice 视图与内存共享
JAVASCRIPT
const original = Buffer.from('ABCDEFGH');
const sliced = original.slice(0, 4);
sliced[0] = 88;
console.log('original:', original.toString());
console.log('sliced:', sliced.toString());
TEXT
📖 仅展示
original: XBCDEFGH
sliced: XBCD
▶ 示例:write 与 copy
JAVASCRIPT
const buf = Buffer.alloc(16);
buf.write('Hi', 0, 'utf8');
buf.write('There', 2, 'utf8');
console.log('after write:', buf.toString('utf8', 0, 7));
const src = Buffer.from('COPY');
const dest = Buffer.alloc(8);
src.copy(dest, 2);
console.log('after copy:', dest.toString());
TEXT
📖 仅展示
after write: HiThere
after copy: COPY
7. Buffer 与 TypedArray
Buffer 的底层内存与 ES2015 的 TypedArray 共享相同的 ArrayBuffer 机制。Buffer 本质上是 Uint8Array 的子类,但拥有额外的 Node.js 专有方法。
| 特性 | Buffer | Uint8Array | ArrayBuffer |
|---|---|---|---|
| 来源 | Node.js 全局对象 | ES2015 内置 | ES2015 内置 |
| 底层机制 | 基于 ArrayBuffer | 基于 ArrayBuffer | 原始二进制内存 |
| 字节序 | 平台相关 | 平台相关 | 无字节序概念 |
| 专有方法 | toString/slice/concat 等 |
标准 TypedArray 方法 | 仅 byteLength |
| 创建方式 | Buffer.alloc/Buffer.from |
new Uint8Array() |
new ArrayBuffer() |
| 跨模块兼容 | 可传给 C++ addon | 可传给 Web API | 通用底层格式 |
▶ 示例:Buffer 与 Uint8Array 互转
JAVASCRIPT
const buf = Buffer.from([1, 2, 3, 4, 5]);
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
console.log('Uint8Array:', uint8);
const backToBuf = Buffer.from(uint8.buffer);
console.log('Buffer:', backToBuf);
console.log('isBuffer:', Buffer.isBuffer(backToBuf));
TEXT
📖 仅展示
Uint8Array: Uint8Array(5) [1, 2, 3, 4, 5]
Buffer: <Buffer 01 02 03 04 05>
isBuffer: true
▶ 示例:用 DataView 读取多字节值
JAVASCRIPT
const buf = Buffer.alloc(4);
buf.writeUInt32BE(0x12345678, 0);
console.log('big-endian:', buf.toString('hex'));
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
console.log('read as uint32:', view.getUint32(0, false));
console.log('read as uint16:', view.getUint16(0, false));
TEXT
📖 仅展示
big-endian: 12345678
read as uint32: 305419896
read as uint16: 4660
8. 二进制文件读写
当使用 fs.readFile 读取文件且不指定编码时,返回值是 Buffer 而非字符串。这是处理图片、音频、视频等二进制文件的标准方式。
▶ 示例:读取二进制文件
JAVASCRIPT
const fs = require('fs');
const imgBuf = fs.readFileSync('photo.jpg');
console.log('isBuffer:', Buffer.isBuffer(imgBuf));
console.log('size:', imgBuf.length, 'bytes');
console.log('first 8 bytes (hex):', imgBuf.slice(0, 8).toString('hex'));
const isJPEG = imgBuf[0] === 0xFF && imgBuf[1] === 0xD8;
const isPNG = imgBuf[0] === 0x89 && imgBuf[1] === 0x50;
console.log('isJPEG:', isJPEG);
console.log('isPNG:', isPNG);
TEXT
📖 仅展示
isBuffer: true
size: 245760 bytes
first 8 bytes (hex): ffd8ffe000104a46
isJPEG: true
isPNG: false
▶ 示例:Buffer 在网络与加密中的应用
JAVASCRIPT
const crypto = require('crypto');
const data = Buffer.from('important message', 'utf8');
const hash = crypto.createHash('sha256').update(data).digest();
console.log('sha256 (hex):', hash.toString('hex'));
const hmac = crypto.createHmac('sha256', 'secret-key').update(data).digest();
console.log('hmac (hex):', hmac.toString('hex'));
const randomBytes = crypto.randomBytes(16);
console.log('random (hex):', randomBytes.toString('hex'));
TEXT
📖 仅展示
sha256 (hex): 8c8821c72b56a55724e9ad64b875e4b62e6c5e9f9c4c4b0c4d5e6f7a8b9c0d1e
hmac (hex): a3f2b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0
random (hex): 3a7f2b1c9d4e8a6f5b0c7d2e1f3a4b8c
9. 综合示例:文件编码工具
构建一个简单的文件编码工具:读取二进制文件 → 转 base64 → 写入文本文件 → 反向解码恢复。
(1) Buffer vs String vs TypedArray 对比
| 维度 | Buffer | String | TypedArray |
|---|---|---|---|
| 存储内容 | 原始字节 | UTF-16 码元 | 特定类型数值 |
| 元素大小 | 固定 1 字节 | 2 字节(UTF-16) | 1~8 字节 |
| 适用数据 | 二进制、文件、网络 | 文本 | 数值数组、WebGL |
| 可变性 | 可修改 | 不可变 | 可修改 |
| 零字节处理 | 正常存储 | 截断字符串 | 正常存储 |
| 编码支持 | utf8/base64/hex 等 | 仅 UTF-16 | 无编码概念 |
JAVASCRIPT
const fs = require('fs');
const path = require('path');
function encodeFile(inputPath, outputPath) {
const raw = fs.readFileSync(inputPath);
const base64 = raw.toString('base64');
fs.writeFileSync(outputPath, base64, 'utf8');
console.log(`Encoded: ${raw.length} bytes → ${base64.length} chars`);
return { originalSize: raw.length, encodedSize: base64.length };
}
function decodeFile(inputPath, outputPath) {
const base64Str = fs.readFileSync(inputPath, 'utf8');
const decoded = Buffer.from(base64Str, 'base64');
fs.writeFileSync(outputPath, decoded);
console.log(`Decoded: ${base64Str.length} chars → ${decoded.length} bytes`);
return { encodedSize: base64Str.length, decodedSize: decoded.length };
}
function verify(originalPath, restoredPath) {
const a = fs.readFileSync(originalPath);
const b = fs.readFileSync(restoredPath);
if (a.equals(b)) {
console.log('Verification: PASSED - files are identical');
} else {
console.log('Verification: FAILED - files differ');
}
}
const inputPath = path.join(__dirname, 'sample.dat');
const encodedPath = path.join(__dirname, 'sample.b64.txt');
const restoredPath = path.join(__dirname, 'sample.restored.dat');
const sampleData = Buffer.alloc(256);
for (let i = 0; i < 256; i++) {
sampleData[i] = i;
}
fs.writeFileSync(inputPath, sampleData);
encodeFile(inputPath, encodedPath);
decodeFile(encodedPath, restoredPath);
verify(inputPath, restoredPath);
TEXT
📖 仅展示
Encoded: 256 bytes → 344 chars
Decoded: 344 chars → 256 bytes
Verification: PASSED - files are identical
❓ 常见问题
Q Buffer.alloc 和 Buffer.allocUnsafe 有什么区别?
A
Buffer.alloc(size) 会将每个字节初始化为 0,安全但稍慢;Buffer.allocUnsafe(size) 不初始化,可能包含旧内存数据,性能更快但必须立即填充,否则可能泄露敏感信息。Q buf.length 返回的是字节数还是字符数?
A
buf.length 返回字节数,与字符串的 str.length(返回 UTF-16 码元数)不同。例如 '你好' 的 str.length 为 2,但 Buffer.byteLength('你好') 为 6(utf8 下每个中文 3 字节)。Q 为什么不用 String 处理二进制数据?
A JavaScript 字符串使用 UTF-16 编码,无法正确表示 0x00 字节(会被截断),且多字节字符与字节的映射关系不直接。Buffer 的每个位置精确对应一个字节,是二进制数据的正确载体。
Q Buffer 是 JavaScript 的一部分吗?
A 不是。Buffer 是 Node.js 特有的全局对象,不属于 ECMAScript 规范。浏览器环境中没有 Buffer,对应的是
Uint8Array / ArrayBuffer 等 Web API。Q 如何判断一个值是不是 Buffer?
A 使用
Buffer.isBuffer(obj),返回 true 或 false。不要用 instanceof,因为跨 realm(如不同 vm 模块)时可能失效。Q buf.slice() 和 buf.subarray() 有什么区别?
A 在 Node.js 中两者行为一致,都返回共享底层内存的视图。
subarray 是为了与 Uint8Array.prototype.subarray 保持命名一致而新增的,slice 保留是为了向后兼容。新代码推荐用 subarray。Q 如何将 Buffer 安全地转换为 JSON?
A 使用
buf.toJSON() 会返回 { type: 'Buffer', data: [...] } 格式的对象,也可直接用 JSON.stringify(buf),它会自动调用 toJSON()。反序列化后需用 Buffer.from(obj.data) 还原。📖 小节
- 你将学到的核心概念与使用方法
- Buffer 是什么的核心概念与使用方法
- Buffer 创建方式的核心概念与使用方法
- 编码与解码的核心概念与使用方法
- Buffer 与 String 互转的核心概念与使用方法
- Buffer 操作方法的核心概念与使用方法
- Buffer 与 TypedArray的核心概念与使用方法
- 二进制文件读写的核心概念与使用方法
📝 作业
- 完成本课所有代码示例,确保每个示例都能正确运行
- 修改综合示例,添加自己的扩展功能
- 查阅官方文档,找出本课未涉及的1-2个API并编写测试代码
- 思考:在实际项目中,你会如何应用本课学到的知识?
- 尝试将本课知识与前面课程的内容结合,构建一个小项目