Node.js 开发指南 / 第 9 章 · Buffer 与二进制数据
第 9 章 · Buffer 与二进制数据
9.1 什么是 Buffer
Buffer 是 Node.js 中用于处理二进制数据的类。由于 JavaScript 原生不支持二进制数据操作,Node.js 引入了 Buffer 来处理文件 I/O、网络协议、加密等场景。
// Buffer 在全局可用,无需 require
console.log(typeof Buffer); // 'function'
Buffer 的特点
| 特性 | 说明 |
|---|---|
| 固定长度 | 创建后大小不可改变 |
| 内存分配 | 直接在 V8 堆外分配内存 |
| 编码支持 | 支持 UTF-8、ASCII、Base64、Hex 等 |
| 与 Stream 配合 | 流的数据块就是 Buffer |
9.2 创建 Buffer
// ⚠️ new Buffer() 已废弃,不要使用!
// ✅ Buffer.alloc(size, fill, encoding) — 安全创建
const buf1 = Buffer.alloc(10); // 10 字节,填充 0
const buf2 = Buffer.alloc(10, 0x61); // 10 字节,填充 'a'
const buf3 = Buffer.alloc(10, 'hello'); // 10 字节,用 'hello' 填充
// ✅ Buffer.allocUnsafe(size) — 不安全但快
const buf4 = Buffer.allocUnsafe(10); // 可能包含旧数据!
buf4.fill(0); // 应立即填充
// ✅ Buffer.from() — 从已有数据创建
const buf5 = Buffer.from([1, 2, 3, 4, 5]);
const buf6 = Buffer.from('Hello, World!');
const buf7 = Buffer.from('你好', 'utf8');
const buf8 = Buffer.from('48656c6c6f', 'hex'); // 'Hello'
const buf9 = Buffer.from('SGVsbG8=', 'base64'); // 'Hello'
// ✅ Buffer.allocUnsafeSlow(size) — 不使用预分配的内存池
const buf10 = Buffer.allocUnsafeSlow(10);
alloc vs allocUnsafe
console.time('alloc');
for (let i = 0; i < 100000; i++) Buffer.alloc(1024);
console.timeEnd('alloc'); // 较慢
console.time('allocUnsafe');
for (let i = 0; i < 100000; i++) Buffer.allocUnsafe(1024);
console.timeEnd('allocUnsafe'); // 较快
// 结论:性能敏感场景使用 allocUnsafe + fill
// 安全敏感场景使用 alloc
9.3 Buffer 操作
基本操作
const buf = Buffer.from('Hello, Node.js!');
// 长度
console.log(buf.length); // 15
// 读取字节
console.log(buf[0]); // 72 (H 的 ASCII 码)
// 修改字节
buf[0] = 0x68; // 改为 'h'
// 转为字符串
console.log(buf.toString()); // 'hello, Node.js!'
console.log(buf.toString('utf8')); // 'hello, Node.js!'
console.log(buf.toString('ascii')); // 'hello, Node.js!'
console.log(buf.toString('hex')); // '68656c6c6f...'
console.log(buf.toString('base64')); // 'aGVsbG8s...'
console.log(buf.toString('utf8', 0, 5)); // 'hello' (部分解码)
// JSON 序列化
console.log(buf.toJSON());
// { type: 'Buffer', data: [104, 101, 108, 108, 111, ...] }
切片与拷贝
const original = Buffer.from('Hello, World!');
// slice — 创建视图(共享内存!)
const slice = original.subarray(0, 5);
console.log(slice.toString()); // 'Hello'
// ⚠️ 修改切片会影响原始 Buffer
slice[0] = 0x48; // 'H'
console.log(original.toString()); // 'Hello, World!' (没变,因为值相同)
// ✅ 安全拷贝
const copy = Buffer.alloc(5);
original.copy(copy, 0, 0, 5);
console.log(copy.toString()); // 'Hello'
// copy 的参数:copy(target, targetStart, sourceStart, sourceEnd)
const target = Buffer.alloc(20);
original.copy(target, 5, 0, 5);
console.log(target.toString()); // '\x00\x00\x00\x00\x00Hello...'
拼接 Buffer
const buf1 = Buffer.from('Hello');
const buf2 = Buffer.from(', ');
const buf3 = Buffer.from('World!');
// Buffer.concat — 拼接多个 Buffer
const result = Buffer.concat([buf1, buf2, buf3]);
console.log(result.toString()); // 'Hello, World!'
// 指定总长度
const result2 = Buffer.concat([buf1, buf2, buf3], 8);
console.log(result2.toString()); // 'Hello, W'
9.4 编码
const text = '你好,世界!';
// UTF-8 编码(默认)
const utf8Buf = Buffer.from(text, 'utf8');
console.log(utf8Buf.length); // 18(每个中文 3 字节)
// UTF-16LE 编码
const utf16Buf = Buffer.from(text, 'utf16le');
console.log(utf16Buf.length); // 14(每个中文 2 字节 + 标点 1 字符 = 2 字节)
// Base64 编码
const base64 = Buffer.from('Hello').toString('base64');
console.log(base64); // 'SGVsbG8='
// Base64 解码
const fromBase64 = Buffer.from('SGVsbG8=', 'base64');
console.log(fromBase64.toString()); // 'Hello'
// Hex 编码
const hex = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]).toString('hex');
console.log(hex); // '48656c6c6f'
// URL 安全的 Base64
function base64UrlEncode(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
中文字符长度
// 字符长度 vs 字节长度
const str = '你好';
console.log(str.length); // 2(字符数)
console.log(Buffer.byteLength(str)); // 6(字节数,UTF-8)
console.log(Buffer.byteLength(str, 'utf16le')); // 4
// 统计实际显示宽度
function getStringWidth(str) {
let width = 0;
for (const char of str) {
const code = char.codePointAt(0);
// CJK 字符宽度为 2
if (code >= 0x4e00 && code <= 0x9fff) {
width += 2;
} else {
width += 1;
}
}
return width;
}
console.log(getStringWidth('Hello你好')); // 9
9.5 Buffer 与 TypedArray
// Buffer 与 Uint8Array 共享底层内存
const buf = Buffer.from([1, 2, 3, 4]);
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
console.log(uint8); // Uint8Array [1, 2, 3, 4]
uint8[0] = 255;
console.log(buf[0]); // 255(共享内存!)
// Uint8Array → Buffer
const arr = new Uint8Array([10, 20, 30]);
const bufFromArr = Buffer.from(arr);
// ArrayBuffer → Buffer
const ab = new ArrayBuffer(8);
const bufFromAB = Buffer.from(ab);
// 注意:Buffer.from(arrayBuffer) 共享内存
// Buffer.from(arrayBuffer, byteOffset, length) 也共享内存
9.6 实用场景
计算文件哈希
const crypto = require('crypto');
const fs = require('fs');
// 同步计算
function hashFile(filePath, algorithm = 'sha256') {
const data = fs.readFileSync(filePath);
return crypto.createHash(algorithm).update(data).digest('hex');
}
console.log(hashFile('package.json'));
// 流式计算(大文件)
async function hashFileStream(filePath) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256');
fs.createReadStream(filePath)
.on('data', (chunk) => hash.update(chunk))
.on('end', () => resolve(hash.digest('hex')))
.on('error', reject);
});
}
Base64 图片
const fs = require('fs');
// 图片转 Base64
function imageToBase64(filePath) {
const data = fs.readFileSync(filePath);
const ext = path.extname(filePath).slice(1);
return `data:image/${ext};base64,${data.toString('base64')}`;
}
// Base64 转图片
function base64ToImage(base64String, outputPath) {
const base64Data = base64String.replace(/^data:image\/\w+;base64,/, '');
fs.writeFileSync(outputPath, Buffer.from(base64Data, 'base64'));
}
二进制协议解析
// 解析自定义二进制协议
function parsePacket(buffer) {
let offset = 0;
// 协议格式:[魔数 2B][版本 1B][类型 1B][长度 4B][数据 NB]
const magic = buffer.readUInt16BE(offset); offset += 2;
const version = buffer.readUInt8(offset); offset += 1;
const type = buffer.readUInt8(offset); offset += 1;
const length = buffer.readUInt32BE(offset); offset += 4;
const data = buffer.subarray(offset, offset + length);
return { magic, version, type, length, data };
}
// 构建数据包
function buildPacket(type, data) {
const header = Buffer.alloc(8);
header.writeUInt16BE(0xABCD, 0); // 魔数
header.writeUInt8(1, 2); // 版本
header.writeUInt8(type, 3); // 类型
header.writeUInt32BE(data.length, 4); // 长度
return Buffer.concat([header, data]);
}
注意事项
⚠️ 永远不要使用
new Buffer():已废弃,存在安全隐患。使用Buffer.from()或Buffer.alloc()。
⚠️
subarray()共享内存:对切片的修改会影响原始 Buffer。需要独立副本时使用Buffer.from()。
⚠️
allocUnsafe可能泄露数据:未初始化的 Buffer 可能包含敏感信息,生产环境应使用alloc()。
⚠️ Buffer 大小限制:单个 Buffer 最大
2GB - 1字节(32 位系统为1GB - 1)。
业务场景
- 文件哈希校验:计算 SHA-256 用于文件完整性验证
- 图片处理:读取图片元数据、格式转换
- 网络协议:实现 TCP/WebSocket 自定义协议
- 加密解密:AES/RSA 加密操作的基础数据类型
扩展阅读
上一章:第 8 章 · 流(Streams) 下一章:第 10 章 · 文件系统 — fs 模块、文件读写、目录操作和文件监听。