Node.js Buffer 完全指南:二进制数据处理、编解码与性能优化实战

引言

在 Node.js 中,Buffer 是一个用于处理二进制数据的全局对象。JavaScript 语言本身擅长处理字符串,但在网络通信、文件操作、流处理等场景下,经常需要与二进制数据打交道。Buffer 提供了一种高效的方式来处理原始字节数据,是 Node.js 核心模块中最重要的组件之一。本文将全面介绍 Buffer 的使用方法、内部原理以及在实际开发中的应用场景。

为什么需要 Buffer

JavaScript 字符串的局限性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────────────┐
│ JavaScript 字符串 vs Buffer │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ JavaScript 字符串: │
│ ├── 采用 UCS-2/UTF-16 编码 │
│ ├── 每个字符固定 2 字节 │
│ ├── 不适合处理二进制数据 │
│ └── 处理图片、音频等文件效率低 │
│ │
│ Buffer: │
│ ├── 直接操作内存中的原始字节 │
│ ├── 支持各种编码格式转换 │
│ ├── 高效的二进制数据处理 │
│ └── 与文件系统、网络 API 无缝集成 │
│ │
│ 典型应用场景: │
│ ├── 文件读写(图片、PDF、视频) │
│ ├── 网络通信(TCP/UDP 数据包) │
│ ├── 加密解密(哈希计算、对称加密) │
│ └── 流处理(视频流、音频流) │
│ │
└─────────────────────────────────────────────────────────────────────┘

Buffer 与 ArrayBuffer 的区别

特性 Buffer (Node.js) ArrayBuffer (浏览器)
运行环境 Node.js 专用 浏览器/Node.js 通用
可变性 可变(内容可修改) 不可变,需通过 TypedArray 操作
API 丰富度 丰富(编码转换、切片、填充等) 基础,需配合 TypedArray
性能 高(直接内存操作) 中等

Buffer 的创建方式

方式一:Buffer.alloc() - 安全分配(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
// 创建一个长度为 10 字节的 Buffer,内容初始化为 0
const buf1 = Buffer.alloc(10);
console.log(buf1); // <Buffer 00 00 00 00 00 00 00 00 00 00>

// 创建一个长度为 10 字节的 Buffer,内容初始化为 0x1
const buf2 = Buffer.alloc(10, 0x1);
console.log(buf2); // <Buffer 01 01 01 01 01 01 01 01 01 01>

// 创建 Buffer 并填充字符串
const buf3 = Buffer.alloc(10, 'a');
console.log(buf3); // <Buffer 61 61 61 61 61 61 61 61 61 61>
console.log(buf3.toString()); // 'aaaaaaaaaa'

方式二:Buffer.allocUnsafe() - 快速分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 分配内存但不初始化,可能包含旧数据
const buf = Buffer.allocUnsafe(10);
console.log(buf); // 内容不确定,取决于之前内存中的数据

// 使用前必须手动填充
buf.fill(0); // 清零

// 性能对比
console.time('alloc');
for (let i = 0; i < 100000; i++) {
Buffer.alloc(1024);
}
console.timeEnd('alloc'); // ~50ms

console.time('allocUnsafe');
for (let i = 0; i < 100000; i++) {
Buffer.allocUnsafe(1024);
}
console.timeEnd('allocUnsafe'); // ~10ms

方式三:从数组创建

1
2
3
4
5
6
7
8
9
// 从数组创建(元素必须是 0-255 的数字)
const buf1 = Buffer.from([0x41, 0x42, 0x43]);
console.log(buf1); // <Buffer 41 42 43>
console.log(buf1.toString()); // 'ABC'

// 从 Uint8Array 创建
const arr = new Uint8Array([1, 2, 3]);
const buf2 = Buffer.from(arr);
console.log(buf2); // <Buffer 01 02 03>

方式四:从字符串创建

1
2
3
4
5
6
7
8
9
10
11
12
// 默认 UTF-8 编码
const buf1 = Buffer.from('Hello World');
console.log(buf1); // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>

// 指定编码
const buf2 = Buffer.from('Hello', 'ascii');
const buf3 = Buffer.from('Hello', 'utf16le');
const buf4 = Buffer.from('48656c6c6f', 'hex'); // 从十六进制字符串
const buf5 = Buffer.from('SGVsbG8=', 'base64'); // 从 Base64 字符串

console.log(buf4.toString()); // 'Hello'
console.log(buf5.toString()); // 'Hello'

创建方式对比

方法 初始化 性能 安全性 使用场景
Buffer.alloc() 填充 0 较慢 安全 一般情况(推荐)
Buffer.allocUnsafe() 不初始化 需手动清理 性能敏感场景
Buffer.from() 根据数据源 中等 安全 已有数据转换

Buffer 与字符串的转换

Buffer 转字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const buf = Buffer.from('Hello 世界');

// 默认 UTF-8 编码
console.log(buf.toString()); // 'Hello 世界'

// 指定编码
console.log(buf.toString('hex')); // '48656c6c6f20e4b896e7958c'
console.log(buf.toString('base64')); // 'SGVsbG8g5LiW55WM'

// 截取部分内容
console.log(buf.toString('utf8', 0, 5)); // 'Hello'
console.log(buf.toString('utf8', 6)); // '世界'

// 支持的编码格式
const encodings = ['utf8', 'utf16le', 'latin1', 'ascii', 'base64', 'hex', 'binary'];

字符串转 Buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
// 写入字符串到 Buffer
const buf = Buffer.alloc(256);
const len = buf.write('Hello 世界', 0, 'utf8');
console.log(len); // 写入的字节数(12)
console.log(buf.toString('utf8', 0, len)); // 'Hello 世界'

// 分段写入
const buf2 = Buffer.alloc(100);
let offset = 0;
offset += buf2.write('Hello', offset);
offset += buf2.write(' ', offset);
offset += buf2.write('World', offset);
console.log(buf2.toString('utf8', 0, offset)); // 'Hello World'

Buffer 数据读写操作

整数读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const buf = Buffer.alloc(8);

// 写入整数(小端序 - Little Endian)
buf.writeUInt8(0x12, 0); // 1 字节无符号整数
buf.writeUInt16LE(0x1234, 1); // 2 字节无符号整数
buf.writeUInt32LE(0x12345678, 3); // 4 字节无符号整数

console.log(buf);
// <Buffer 12 34 12 78 56 34 12 00>

// 读取整数
console.log(buf.readUInt8(0)); // 18 (0x12)
console.log(buf.readUInt16LE(1)); // 4660 (0x1234)
console.log(buf.readUInt32LE(3)); // 305419896 (0x12345678)

// 大端序 - Big Endian
const buf2 = Buffer.alloc(4);
buf2.writeUInt16BE(0x1234, 0);
buf2.writeUInt16BE(0x5678, 2);
console.log(buf2); // <Buffer 12 34 56 78>

支持的数值类型

方法 说明 字节数
readUInt8 / writeUInt8 无符号 8 位整数 1
readInt8 / writeInt8 有符号 8 位整数 1
readUInt16LE/BE / writeUInt16LE/BE 无符号 16 位整数 2
readInt16LE/BE / writeInt16LE/BE 有符号 16 位整数 2
readUInt32LE/BE / writeUInt32LE/BE 无符号 32 位整数 4
readInt32LE/BE / writeInt32LE/BE 有符号 32 位整数 4
readFloatLE/BE / writeFloatLE/BE 32 位浮点数 4
readDoubleLE/BE / writeDoubleLE/BE 64 位浮点数 8
readBigUInt64LE/BE / writeBigUInt64LE/BE 64 位大整数 8

字节操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 填充
const buf = Buffer.alloc(10);
buf.fill('a');
console.log(buf.toString()); // 'aaaaaaaaaa'

buf.fill('b', 2, 5);
console.log(buf.toString()); // 'aabbbbaaaa'

// 复制
const source = Buffer.from('Hello World');
const target = Buffer.alloc(5);
source.copy(target, 0, 0, 5);
console.log(target.toString()); // 'Hello'

// 切片(创建视图,不复制数据)
const buf2 = Buffer.from('Hello World');
const slice = buf2.slice(0, 5);
console.log(slice.toString()); // 'Hello'

// 修改切片会影响原 Buffer
slice[0] = 0x68; // 'h'
console.log(buf2.toString()); // 'hello World'

// 使用 subarray(Buffer 6.0+ 推荐使用)
const sub = buf2.subarray(6, 11);
console.log(sub.toString()); // 'World'

Buffer 的常用方法

Buffer 类静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 判断是否为 Buffer
const buf = Buffer.from('test');
console.log(Buffer.isBuffer(buf)); // true
console.log(Buffer.isBuffer('test')); // false

// 获取字符串的字节长度
console.log(Buffer.byteLength('Hello')); // 5
console.log(Buffer.byteLength('世界')); // 6(UTF-8 编码)
console.log(Buffer.byteLength('Hello', 'utf16le')); // 10

// 判断编码是否支持
console.log(Buffer.isEncoding('utf8')); // true
console.log(Buffer.isEncoding('gbk')); // false

// 拼接多个 Buffer
const buf1 = Buffer.from('Hello');
const buf2 = Buffer.from(' ');
const buf3 = Buffer.from('World');
const combined = Buffer.concat([buf1, buf2, buf3]);
console.log(combined.toString()); // 'Hello World'

// 指定总长度(性能优化)
const combined2 = Buffer.concat([buf1, buf2, buf3], 11);

// 比较 Buffer
const bufA = Buffer.from('A');
const bufB = Buffer.from('B');
console.log(Buffer.compare(bufA, bufB)); // -1 (A < B)

// 排序
const bufs = [Buffer.from('C'), Buffer.from('A'), Buffer.from('B')];
bufs.sort(Buffer.compare);
console.log(bufs.map(b => b.toString())); // ['A', 'B', 'C']

实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const buf = Buffer.from('Hello World');

// 查找索引
console.log(buf.indexOf('World')); // 6
console.log(buf.indexOf('world')); // -1(大小写敏感)
console.log(buf.indexOf(0x6c)); // 2 ('l' 的 ASCII 码)

// 是否包含
console.log(buf.includes('World')); // true

// 迭代
for (const byte of buf) {
console.log(byte); // 72, 101, 108, 108, 111...
}

// entries / keys / values
for (const [index, byte] of buf.entries()) {
console.log(`${index}: ${byte}`);
}

// 转换为 JSON
const json = buf.toJSON();
console.log(json); // { type: 'Buffer', data: [72, 101, 108, 108, 111...] }

// 从 JSON 恢复
const restored = Buffer.from(json.data);
console.log(restored.toString()); // 'Hello World'

实际应用场景

场景一:文件读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const fs = require('fs');

// 读取图片文件
const imageBuffer = fs.readFileSync('image.png');
console.log(imageBuffer.length); // 文件大小(字节)
console.log(imageBuffer.slice(0, 8).toString('hex')); // PNG 文件头

// 检查文件类型(通过魔数)
function getFileType(buffer) {
const magic = buffer.slice(0, 4).toString('hex');
switch (magic) {
case '89504e47': return 'image/png';
case 'ffd8ffe0':
case 'ffd8ffe1': return 'image/jpeg';
case '47494638': return 'image/gif';
case '25504446': return 'application/pdf';
default: return 'unknown';
}
}

// 写入二进制文件
const data = Buffer.from([0x89, 0x50, 0x4e, 0x47]); // PNG 文件头
fs.writeFileSync('test.png', data);

// 流式读取(大文件)
const stream = fs.createReadStream('large-file.bin', {
highWaterMark: 1024 // 每次读取 1KB
});

stream.on('data', (chunk) => {
console.log(`Received ${chunk.length} bytes`);
// 处理 chunk(Buffer)
});

场景二:网络通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const net = require('net');

// 创建 TCP 服务器
const server = net.createServer((socket) => {
socket.on('data', (data) => {
// data 是 Buffer
console.log('Received:', data.toString('hex'));

// 解析协议头
const version = data.readUInt8(0);
const command = data.readUInt16BE(1);
const payloadLength = data.readUInt32BE(3);

console.log(`Version: ${version}, Command: ${command}, Length: ${payloadLength}`);
});

// 发送二进制数据
const response = Buffer.alloc(8);
response.writeUInt8(1, 0); // version
response.writeUInt16BE(200, 1); // status
response.writeUInt32BE(0, 3); // reserved
socket.write(response);
});

server.listen(3000);

// HTTP 请求体处理
const http = require('http');
const server2 = http.createServer((req, res) => {
const chunks = [];

req.on('data', (chunk) => {
chunks.push(chunk);
});

req.on('end', () => {
const body = Buffer.concat(chunks);
console.log('Body length:', body.length);
console.log('Body:', body.toString());
});
});

场景三:编解码转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Base64 编解码
const original = 'Hello 世界';
const base64 = Buffer.from(original).toString('base64');
console.log(base64); // SGVsbG8g5LiW55WM

const decoded = Buffer.from(base64, 'base64').toString();
console.log(decoded); // Hello 世界

// URL 安全的 Base64
function urlSafeBase64(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}

// Hex 编解码
const hex = Buffer.from('Hello').toString('hex');
console.log(hex); // 48656c6c6f

const fromHex = Buffer.from(hex, 'hex').toString();
console.log(fromHex); // Hello

// 多字节字符处理
const chinese = '中文字符测试';
const buf = Buffer.from(chinese);
console.log(buf.length); // 18 字节(UTF-8)

// 正确处理截断(避免乱码)
function safeSlice(buffer, start, end) {
const sliced = buffer.slice(start, end);
// 检查是否截断了多字节字符
try {
return sliced.toString('utf8');
} catch (e) {
// 移除不完整的字节
for (let i = sliced.length - 1; i >= 0; i--) {
if ((sliced[i] & 0xc0) !== 0x80) {
return sliced.slice(0, i).toString('utf8');
}
}
return '';
}
}

场景四:加密解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const crypto = require('crypto');

// 计算哈希
const data = Buffer.from('Hello World');
const hash = crypto.createHash('sha256').update(data).digest();
console.log(hash.toString('hex'));

// 对称加密
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);

function encrypt(text) {
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
return encrypted;
}

function decrypt(encrypted) {
const decipher = crypto.createDecipheriv(algorithm, key, iv);
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
return decrypted.toString('utf8');
}

const encrypted = encrypt('Secret Message');
console.log('Encrypted:', encrypted.toString('hex'));
console.log('Decrypted:', decrypt(encrypted));

// HMAC
const hmac = crypto.createHmac('sha256', 'secret-key');
hmac.update(data);
console.log(hmac.digest().toString('hex'));

Buffer 性能优化

避免不必要的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 低效:多次拷贝
function processFileBad(buffer) {
const step1 = buffer.slice(0, 100); // 创建新视图
const step2 = Buffer.from(step1); // 复制数据!
const step3 = step2.toString(); // 转换字符串
return step3;
}

// 高效:最小化拷贝
function processFileGood(buffer) {
// slice 不拷贝数据
const slice = buffer.subarray(0, 100);
// 直接转换,无需复制
return slice.toString('utf8', 0, slice.length);
}

// 池化 Buffer(高频创建场景)
class BufferPool {
constructor(size) {
this.pool = Buffer.allocUnsafe(size);
this.offset = 0;
this.maxSize = size;
}

allocate(size) {
if (this.offset + size > this.maxSize) {
// 池已满,创建新的 Buffer
return Buffer.allocUnsafe(size);
}
const buf = this.pool.slice(this.offset, this.offset + size);
this.offset += size;
return buf;
}

reset() {
this.offset = 0;
}
}

使用流处理大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const fs = require('fs');
const crypto = require('crypto');

// 流式计算大文件哈希(内存友好)
function hashFile(filename, algorithm = 'sha256') {
return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm);
const stream = fs.createReadStream(filename);

stream.on('error', reject);
stream.on('data', (chunk) => hash.update(chunk));
stream.on('end', () => resolve(hash.digest('hex')));
});
}

// 使用 Transform 流处理数据
const { Transform } = require('stream');

const upperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
// chunk 是 Buffer
for (let i = 0; i < chunk.length; i++) {
// 小写转大写
if (chunk[i] >= 97 && chunk[i] <= 122) {
chunk[i] -= 32;
}
}
callback(null, chunk);
}
});

fs.createReadStream('input.txt')
.pipe(upperCaseTransform)
.pipe(fs.createWriteStream('output.txt'));

常见错误与解决方案

错误 原因 解决方案
TypeError: buf is not a Buffer 传入非 Buffer 对象 使用 Buffer.from() 转换
乱码问题 编码不匹配或数据截断 确保编码一致,处理多字节字符边界
内存溢出 创建过大的 Buffer 使用流处理大文件
RangeError 读写越界 检查 offset 和 length
ERR_BUFFER_OUT_OF_BOUNDS Buffer 操作超出范围 验证索引值

总结

Node.js Buffer 的核心要点:

  1. 创建方式:优先使用 Buffer.alloc()Buffer.allocUnsafe() 用于性能敏感场景
  2. 编码转换:熟练掌握 toString()Buffer.from() 的各种编码格式
  3. 数据操作:理解 slice() / subarray() 创建视图而非拷贝
  4. 整数读写:注意字节序(LE/BE)和数据类型匹配
  5. 性能优化:避免不必要的拷贝,使用流处理大数据
  6. 应用场景:文件 I/O、网络通信、编解码、加密解密等
  7. 安全注意allocUnsafe() 使用前必须清理敏感数据

Buffer 是 Node.js 处理二进制数据的基础,掌握其使用方法和内部原理,对于开发高性能的 Node.js 应用至关重要。