引言
在 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
| const buf1 = Buffer.alloc(10); console.log(buf1);
const buf2 = Buffer.alloc(10, 0x1); console.log(buf2);
const buf3 = Buffer.alloc(10, 'a'); console.log(buf3); console.log(buf3.toString());
|
方式二: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');
console.time('allocUnsafe'); for (let i = 0; i < 100000; i++) { Buffer.allocUnsafe(1024); } console.timeEnd('allocUnsafe');
|
方式三:从数组创建
1 2 3 4 5 6 7 8 9
| const buf1 = Buffer.from([0x41, 0x42, 0x43]); console.log(buf1); console.log(buf1.toString());
const arr = new Uint8Array([1, 2, 3]); const buf2 = Buffer.from(arr); console.log(buf2);
|
方式四:从字符串创建
1 2 3 4 5 6 7 8 9 10 11 12
| const buf1 = Buffer.from('Hello World'); console.log(buf1);
const buf2 = Buffer.from('Hello', 'ascii'); const buf3 = Buffer.from('Hello', 'utf16le'); const buf4 = Buffer.from('48656c6c6f', 'hex'); const buf5 = Buffer.from('SGVsbG8=', 'base64');
console.log(buf4.toString()); console.log(buf5.toString());
|
创建方式对比
| 方法 |
初始化 |
性能 |
安全性 |
使用场景 |
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 世界');
console.log(buf.toString());
console.log(buf.toString('hex')); console.log(buf.toString('base64'));
console.log(buf.toString('utf8', 0, 5)); 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
| const buf = Buffer.alloc(256); const len = buf.write('Hello 世界', 0, 'utf8'); console.log(len); console.log(buf.toString('utf8', 0, len));
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));
|
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);
buf.writeUInt8(0x12, 0); buf.writeUInt16LE(0x1234, 1); buf.writeUInt32LE(0x12345678, 3);
console.log(buf);
console.log(buf.readUInt8(0)); console.log(buf.readUInt16LE(1)); console.log(buf.readUInt32LE(3));
const buf2 = Buffer.alloc(4); buf2.writeUInt16BE(0x1234, 0); buf2.writeUInt16BE(0x5678, 2); console.log(buf2);
|
支持的数值类型
| 方法 |
说明 |
字节数 |
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());
buf.fill('b', 2, 5); console.log(buf.toString());
const source = Buffer.from('Hello World'); const target = Buffer.alloc(5); source.copy(target, 0, 0, 5); console.log(target.toString());
const buf2 = Buffer.from('Hello World'); const slice = buf2.slice(0, 5); console.log(slice.toString());
slice[0] = 0x68; console.log(buf2.toString());
const sub = buf2.subarray(6, 11); console.log(sub.toString());
|
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
| const buf = Buffer.from('test'); console.log(Buffer.isBuffer(buf)); console.log(Buffer.isBuffer('test'));
console.log(Buffer.byteLength('Hello')); console.log(Buffer.byteLength('世界')); console.log(Buffer.byteLength('Hello', 'utf16le'));
console.log(Buffer.isEncoding('utf8')); console.log(Buffer.isEncoding('gbk'));
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());
const combined2 = Buffer.concat([buf1, buf2, buf3], 11);
const bufA = Buffer.from('A'); const bufB = Buffer.from('B'); console.log(Buffer.compare(bufA, bufB));
const bufs = [Buffer.from('C'), Buffer.from('A'), Buffer.from('B')]; bufs.sort(Buffer.compare); console.log(bufs.map(b => b.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
| const buf = Buffer.from('Hello World');
console.log(buf.indexOf('World')); console.log(buf.indexOf('world')); console.log(buf.indexOf(0x6c));
console.log(buf.includes('World'));
for (const byte of buf) { console.log(byte); }
for (const [index, byte] of buf.entries()) { console.log(`${index}: ${byte}`); }
const json = buf.toJSON(); console.log(json);
const restored = Buffer.from(json.data); console.log(restored.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
| const fs = require('fs');
const imageBuffer = fs.readFileSync('image.png'); console.log(imageBuffer.length); console.log(imageBuffer.slice(0, 8).toString('hex'));
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]); fs.writeFileSync('test.png', data);
const stream = fs.createReadStream('large-file.bin', { highWaterMark: 1024 });
stream.on('data', (chunk) => { console.log(`Received ${chunk.length} bytes`); });
|
场景二:网络通信
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');
const server = net.createServer((socket) => { socket.on('data', (data) => { 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); response.writeUInt16BE(200, 1); response.writeUInt32BE(0, 3); socket.write(response); });
server.listen(3000);
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
| const original = 'Hello 世界'; const base64 = Buffer.from(original).toString('base64'); console.log(base64);
const decoded = Buffer.from(base64, 'base64').toString(); console.log(decoded);
function urlSafeBase64(buffer) { return buffer.toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); }
const hex = Buffer.from('Hello').toString('hex'); console.log(hex);
const fromHex = Buffer.from(hex, 'hex').toString(); console.log(fromHex);
const chinese = '中文字符测试'; const buf = Buffer.from(chinese); console.log(buf.length);
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));
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) { const slice = buffer.subarray(0, 100); return slice.toString('utf8', 0, slice.length); }
class BufferPool { constructor(size) { this.pool = Buffer.allocUnsafe(size); this.offset = 0; this.maxSize = size; }
allocate(size) { if (this.offset + size > this.maxSize) { 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'))); }); }
const { Transform } = require('stream');
const upperCaseTransform = new Transform({ transform(chunk, encoding, callback) { 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 的核心要点:
- 创建方式:优先使用
Buffer.alloc(),Buffer.allocUnsafe() 用于性能敏感场景
- 编码转换:熟练掌握
toString() 和 Buffer.from() 的各种编码格式
- 数据操作:理解
slice() / subarray() 创建视图而非拷贝
- 整数读写:注意字节序(LE/BE)和数据类型匹配
- 性能优化:避免不必要的拷贝,使用流处理大数据
- 应用场景:文件 I/O、网络通信、编解码、加密解密等
- 安全注意:
allocUnsafe() 使用前必须清理敏感数据
Buffer 是 Node.js 处理二进制数据的基础,掌握其使用方法和内部原理,对于开发高性能的 Node.js 应用至关重要。