Node.js Buffer 缓冲区
Buffer 是 Node.js 中用于处理二进制数据的核心模块。它类似于数组,但专门用于存储原始字节数据。
什么是 Buffer?
Buffer 是一个表示固定长度字节序列的类。在 Node.js 中,许多 API(如文件系统、网络操作)都使用 Buffer 来处理二进制数据。
为什么需要 Buffer?
JavaScript 原生不支持二进制数据的直接操作。在 Node.js 中,经常需要处理:
- 文件读写(图片、视频等二进制文件)
- 网络数据传输
- 加密解密操作
- 数据库二进制字段
Buffer 就是为了解决这些问题而设计的。
// Buffer 是 Uint8Array 的子类
const buf = Buffer.from('Hello');
console.log(buf);
// <Buffer 48 65 6c 6c 6f>
console.log(buf.length); // 5
console.log(buf[0]); // 72 (字符 'H' 的 ASCII 码)
解释:
- Buffer 存储的是原始字节数据(0-255 之间的整数)
- 每个字节用两个十六进制字符表示
- Buffer 可以像数组一样通过索引访问
创建 Buffer
Buffer.from()
从现有数据创建 Buffer:
const { Buffer } = require('buffer');
// 从字符串创建
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', 'utf8');
const buf3 = Buffer.from('Hello', 'base64');
// 从数组创建
const buf4 = Buffer.from([72, 101, 108, 108, 111]);
console.log(buf4.toString()); // 'Hello'
// 从另一个 Buffer 创建
const buf5 = Buffer.from(buf1);
// 从 ArrayBuffer 创建
const arr = new Uint16Array(2);
arr[0] = 5000;
arr[1] = 4000;
const buf6 = Buffer.from(arr.buffer);
console.log(buf6);
// <Buffer 88 13 a0 0f>
解释:
Buffer.from(string)默认使用 UTF-8 编码- 数组中的值会被截断为 0-255 范围(
value & 255) - 从 ArrayBuffer 创建时,Buffer 和原 TypedArray 共享内存
Buffer.alloc()
创建指定大小的 Buffer,并用 0 填充:
const { Buffer } = require('buffer');
// 创建 10 字节的 Buffer,全部填充为 0
const buf1 = Buffer.alloc(10);
console.log(buf1);
// <Buffer 00 00 00 00 00 00 00 00 00 00>
// 创建并填充特定值
const buf2 = Buffer.alloc(10, 'a');
console.log(buf2);
// <Buffer 61 61 61 61 61 61 61 61 61 61>
// 使用字符串和编码填充
const buf3 = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');
console.log(buf3.toString());
// 'hello world'
解释:
Buffer.alloc()是安全的创建方式,Buffer 会被初始化为 0- 可以指定填充值,支持字符串和数字
- 适合需要安全、干净 Buffer 的场景
Buffer.allocUnsafe()
创建指定大小的 Buffer,但不初始化:
const { Buffer } = require('buffer');
// 创建未初始化的 Buffer(内容随机)
const buf = Buffer.allocUnsafe(10);
console.log(buf);
// <Buffer a0 8b 28 3f 01 00 00 00 50 32>(内容不确定)
// 使用前应该手动初始化
buf.fill(0);
console.log(buf);
// <Buffer 00 00 00 00 00 00 00 00 00 00>
解释:
allocUnsafe比alloc更快,因为不需要初始化- 但 Buffer 可能包含敏感数据(之前分配的内存内容)
- 除非确定数据会被完全覆盖,否则建议使用
alloc
Buffer.allocUnsafeSlow()
创建不使用内存池的 Buffer:
const { Buffer } = require('buffer');
// 创建不使用内存池的 Buffer
const buf = Buffer.allocUnsafeSlow(10);
解释:
- 通常用于需要长时间保留小块内存的场景
- 避免小块内存占用内存池影响其他分配
- 大多数情况下不需要使用
Buffer 编码
支持的字符编码
const { Buffer } = require('buffer');
const str = '你好世界';
// UTF-8(默认)
const utf8Buf = Buffer.from(str, 'utf8');
console.log(utf8Buf);
// <Buffer e4 bd a0 e5 a5 bd e4 b8 96 e7 95 8c>
// 十六进制
const hexBuf = Buffer.from(str, 'utf8');
console.log(hexBuf.toString('hex'));
// e4bda0e5a5bde4b896e7958c
// Base64
const base64Str = utf8Buf.toString('base64');
console.log(base64Str);
// 5L2g5aW95LiW55WM
// 从 Base64 解码
const decodedBuf = Buffer.from(base64Str, 'base64');
console.log(decodedBuf.toString('utf8'));
// 你好世界
常用编码类型
| 编码 | 描述 |
|---|---|
utf8 | UTF-8 编码,默认编码 |
utf16le | UTF-16 小端编码 |
latin1 | Latin-1 (ISO-8859-1) |
base64 | Base64 编码 |
base64url | URL 安全的 Base64 |
hex | 十六进制编码 |
ascii | 7 位 ASCII 编码 |
binary | Latin-1 的别名 |
编码转换示例
const { Buffer } = require('buffer');
// 字符串 -> Buffer -> Base64
const text = 'Hello, 世界!';
const buf = Buffer.from(text, 'utf8');
const base64 = buf.toString('base64');
console.log('Base64:', base64);
// Base64: SGVsbG8sIOS4lueVjCE=
// Base64 -> Buffer -> 字符串
const decoded = Buffer.from(base64, 'base64');
console.log('解码:', decoded.toString('utf8'));
// 解码: Hello, 世界!
// Buffer -> Hex
const hex = buf.toString('hex');
console.log('Hex:', hex);
// Hex: 48656c6c6f2c20e4b896e7958c21
// Hex -> Buffer
const fromHex = Buffer.from(hex, 'hex');
console.log('从Hex解码:', fromHex.toString('utf8'));
// 从Hex解码: Hello, 世界!
Buffer 操作
读写操作
const { Buffer } = require('buffer');
// 创建一个 Buffer
const buf = Buffer.alloc(16);
// 写入字符串
const bytesWritten = buf.write('Hello', 0, 'utf8');
console.log('写入字节数:', bytesWritten); // 5
// 写入到指定位置
buf.write(' World', 5);
console.log(buf.toString('utf8', 0, 11));
// Hello World
// 读取指定范围
const slice = buf.toString('utf8', 0, 5);
console.log(slice); // Hello
索引访问
const { Buffer } = require('buffer');
const buf = Buffer.from([1, 2, 3, 4, 5]);
// 读取指定位置的字节
console.log(buf[0]); // 1
console.log(buf[2]); // 3
// 修改指定位置的字节
buf[0] = 10;
console.log(buf[0]); // 10
// 超出范围的值会被截断
buf[1] = 300; // 300 & 255 = 44
console.log(buf[1]); // 44
// 负数会被转换
buf[2] = -1; // -1 & 255 = 255
console.log(buf[2]); // 255
解释:
- Buffer 索引从 0 开始
- 写入的值会被截断为 0-255 范围(使用
value & 255) - 负数会被转换为对应的正数
切片和子数组
const { Buffer } = require('buffer');
const buf = Buffer.from('Hello World');
// slice - 创建视图(不复制)
const slice = buf.slice(0, 5);
console.log(slice.toString()); // 'Hello'
// 修改 slice 会影响原 Buffer
slice[0] = 74; // 'J'
console.log(buf.toString()); // 'Jello World'
// subarray - 与 slice 类似,推荐使用
const sub = buf.subarray(6, 11);
console.log(sub.toString()); // 'World'
// 获取整个 Buffer 的副本
const copy = Buffer.from(buf);
copy[0] = 72; // 'H'
console.log(buf.toString()); // 'Jello World'(原 Buffer 不受影响)
解释:
slice()和subarray()创建的是原 Buffer 的视图,修改会影响原 Buffer- 要创建独立副本,使用
Buffer.from(buf)
复制
const { Buffer } = require('buffer');
const src = Buffer.from('Hello World');
const dest = Buffer.alloc(11);
// 复制到目标 Buffer
const copied = src.copy(dest);
console.log(dest.toString()); // 'Hello World'
// 指定源和目标范围
const dest2 = Buffer.alloc(5);
src.copy(dest2, 0, 0, 5); // 复制 src[0-4] 到 dest2[0]
console.log(dest2.toString()); // 'Hello'
// 部分复制
const dest3 = Buffer.alloc(5);
src.copy(dest3, 0, 6, 11); // 复制 'World'
console.log(dest3.toString()); // 'World'
拼接
const { Buffer } = require('buffer');
const buf1 = Buffer.from('Hello ');
const buf2 = Buffer.from('World');
const buf3 = Buffer.from('!');
// 拼接多个 Buffer
const result = Buffer.concat([buf1, buf2, buf3]);
console.log(result.toString()); // 'Hello World!'
// 指定总长度
const padded = Buffer.concat([buf1, buf2], 20);
console.log(padded.toString());
// 'Hello World\x00\x00\x00\x00\x00\x00\x00\x00\x00'
比较
const { Buffer } = require('buffer');
const buf1 = Buffer.from('abc');
const buf2 = Buffer.from('abc');
const buf3 = Buffer.from('abd');
// 比较是否相等
console.log(buf1.equals(buf2)); // true
console.log(buf1.equals(buf3)); // false
// 字典序比较
console.log(buf1.compare(buf2)); // 0(相等)
console.log(buf1.compare(buf3)); // -1(buf1 < buf3)
console.log(buf3.compare(buf1)); // 1(buf3 > buf1)
// 用于排序
const buffers = [Buffer.from('c'), Buffer.from('a'), Buffer.from('b')];
buffers.sort(Buffer.compare);
console.log(buffers.map(b => b.toString())); // ['a', 'b', 'c']
查找
const { Buffer } = require('buffer');
const buf = Buffer.from('Hello World');
// 查找子串位置
console.log(buf.indexOf('World')); // 6
console.log(buf.indexOf('o')); // 4
console.log(buf.indexOf('o', 5)); // 7(从位置5开始查找)
// 从后向前查找
console.log(buf.lastIndexOf('o')); // 7
// 查找 Buffer
const search = Buffer.from('World');
console.log(buf.indexOf(search)); // 6
// 包含检查
console.log(buf.includes('World')); // true
console.log(buf.includes('world')); // false(区分大小写)
数字读写
Buffer 提供了读写各种数值类型的方法,支持大端序(BE)和小端序(LE)。
写入整数
const { Buffer } = require('buffer');
const buf = Buffer.alloc(16);
// 写入 8 位整数
buf.writeInt8(127, 0); // 有符号:-128 到 127
buf.writeUInt8(255, 1); // 无符号:0 到 255
// 写入 16 位整数
buf.writeInt16BE(32767, 2); // 大端序
buf.writeInt16LE(32767, 4); // 小端序
// 写入 32 位整数
buf.writeInt32BE(2147483647, 6); // 大端序
buf.writeInt32LE(2147483647, 10); // 小端序
console.log(buf);
读取整数
const { Buffer } = require('buffer');
const buf = Buffer.from([
0x7F, // int8: 127
0xFF, // uint8: 255
0x7F, 0xFF, // int16 BE: 32767
0xFF, 0x7F, // int16 LE: 32767
]);
// 读取 8 位整数
console.log(buf.readInt8(0)); // 127
console.log(buf.readUInt8(1)); // 255
// 读取 16 位整数
console.log(buf.readInt16BE(2)); // 32767
console.log(buf.readInt16LE(4)); // 32767
浮点数操作
const { Buffer } = require('buffer');
const buf = Buffer.alloc(16);
// 写入浮点数
buf.writeFloatBE(3.14, 0);
buf.writeFloatLE(3.14, 4);
buf.writeDoubleBE(3.14159265358979, 8);
// 读取浮点数
console.log(buf.readFloatBE(0)); // 约 3.14
console.log(buf.readFloatLE(4)); // 约 3.14
console.log(buf.readDoubleBE(8)); // 约 3.14159265358979
BigInt 支持
const { Buffer } = require('buffer');
const buf = Buffer.alloc(8);
// 写入 64 位整数
buf.writeBigInt64BE(BigInt('9223372036854775807'), 0);
buf.writeBigUInt64LE(BigInt('18446744073709551615'), 0);
// 读取 64 位整数
console.log(buf.readBigInt64BE(0));
console.log(buf.readBigUInt64LE(0));
Buffer 属性
const { Buffer } = require('buffer');
const buf = Buffer.from('Hello World');
// 长度
console.log(buf.length); // 11
// 字节偏移
console.log(buf.byteOffset); // 取决于创建方式
// 底层 ArrayBuffer
console.log(buf.buffer); // ArrayBuffer
// 判断是否是 Buffer
console.log(Buffer.isBuffer(buf)); // true
// 判断是否支持某种编码
console.log(Buffer.isEncoding('utf8')); // true
console.log(Buffer.isEncoding('unknown')); // false
Buffer 与 TypedArray
Buffer 是 Uint8Array 的子类,可以与 TypedArray 互相转换。
const { Buffer } = require('buffer');
// Buffer -> TypedArray
const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
const uint32 = new Uint32Array(buf.buffer, buf.byteOffset, buf.length / 4);
console.log(uint32); // Uint32Array(2) [ 67305985, 134678021 ]
// TypedArray -> Buffer
const arr = new Uint16Array([1000, 2000, 3000]);
const buf2 = Buffer.from(arr.buffer);
console.log(buf2); // <Buffer e8 03 d0 07 b8 0b>
// 共享内存示例
const shared = new Uint8Array(5);
shared[0] = 72;
const buf3 = Buffer.from(shared.buffer);
buf3[1] = 101; // 'e'
console.log(shared[1]); // 101(修改了同一个内存)
实用工具函数
获取字节长度
const { Buffer } = require('buffer');
// 字符串的字节长度(不等同于字符数)
const str = '你好';
console.log(str.length); // 2(字符数)
console.log(Buffer.byteLength(str)); // 6(UTF-8 编码下每个中文 3 字节)
// 不同编码的字节长度
console.log(Buffer.byteLength('Hello', 'utf8')); // 5
console.log(Buffer.byteLength('Hello', 'utf16le')); // 10
console.log(Buffer.byteLength('Hello', 'base64')); // 3
填充
const { Buffer } = require('buffer');
const buf = Buffer.alloc(10);
// 填充数字
buf.fill(0);
console.log(buf); // <Buffer 00 00 00 00 00 00 00 00 00 00>
// 填充字符串
buf.fill('abc');
console.log(buf.toString()); // 'abcabcabca'
// 部分填充
buf.fill('x', 5, 8);
console.log(buf.toString()); // 'abcabxxxa'
// 填充 Buffer
const pattern = Buffer.from([1, 2]);
buf.fill(pattern, 0, 6);
console.log(buf); // <Buffer 01 02 01 02 01 02 ...>
迭代
const { Buffer } = require('buffer');
const buf = Buffer.from([1, 2, 3, 4, 5]);
// for...of 迭代
for (const byte of buf) {
console.log(byte);
}
// entries() - 获取 [index, value]
for (const [index, byte] of buf.entries()) {
console.log(`${index}: ${byte}`);
}
// keys() - 获取索引
for (const index of buf.keys()) {
console.log(index);
}
// values() - 获取值
for (const byte of buf.values()) {
console.log(byte);
}
实战示例
Base64 编码解码
const { Buffer } = require('buffer');
// 编码函数
function base64Encode(str) {
return Buffer.from(str, 'utf8').toString('base64');
}
// 解码函数
function base64Decode(base64) {
return Buffer.from(base64, 'base64').toString('utf8');
}
const original = 'Hello, 世界!';
const encoded = base64Encode(original);
const decoded = base64Decode(encoded);
console.log('原始:', original);
console.log('编码:', encoded);
console.log('解码:', decoded);
二进制协议解析
const { Buffer } = require('buffer');
// 模拟一个简单的二进制协议
// 格式: [类型(1字节)][长度(2字节)][数据(N字节)]
function createPacket(type, data) {
const dataBuffer = Buffer.from(data, 'utf8');
const packet = Buffer.alloc(3 + dataBuffer.length);
packet.writeUInt8(type, 0);
packet.writeUInt16BE(dataBuffer.length, 1);
dataBuffer.copy(packet, 3);
return packet;
}
function parsePacket(buffer) {
const type = buffer.readUInt8(0);
const length = buffer.readUInt16BE(1);
const data = buffer.toString('utf8', 3, 3 + length);
return { type, length, data };
}
// 使用
const packet = createPacket(1, 'Hello');
console.log('原始包:', packet);
const parsed = parsePacket(packet);
console.log('解析结果:', parsed);
// { type: 1, length: 5, data: 'Hello' }
文件哈希
const crypto = require('crypto');
const fs = require('fs');
// 使用 Buffer 计算文件哈希
function hashFile(filePath) {
const content = fs.readFileSync(filePath);
const hash = crypto.createHash('sha256').update(content).digest('hex');
return hash;
}
// 或者使用流处理大文件
async function hashFileAsync(filePath) {
const { pipeline } = require('stream/promises');
const hash = crypto.createHash('sha256');
await pipeline(
fs.createReadStream(filePath),
hash
);
return hash.digest('hex');
}
图片处理基础
const fs = require('fs');
const { Buffer } = require('buffer');
// 读取图片并检查格式
function checkImageFormat(filePath) {
const fd = fs.openSync(filePath, 'r');
const header = Buffer.alloc(8);
fs.readSync(fd, header, 0, 8, 0);
fs.closeSync(fd);
// PNG 文件头: 89 50 4E 47 0D 0A 1A 0A
if (header[0] === 0x89 && header.slice(1, 4).toString() === 'PNG') {
return 'PNG';
}
// JPEG 文件头: FF D8 FF
if (header[0] === 0xFF && header[1] === 0xD8 && header[2] === 0xFF) {
return 'JPEG';
}
// GIF 文件头: GIF87a 或 GIF89a
if (header.slice(0, 3).toString() === 'GIF') {
return 'GIF';
}
return 'Unknown';
}
console.log(checkImageFormat('image.png'));
最佳实践
1. 选择正确的创建方法
// ✅ 需要安全、干净的 Buffer
const buf1 = Buffer.alloc(10);
// ✅ 性能敏感,且会完全覆盖内容
const buf2 = Buffer.allocUnsafe(10);
buf2.fill(0); // 手动初始化
// ✅ 从现有数据创建
const buf3 = Buffer.from('Hello');
2. 处理大端/小端
// 网络协议通常使用大端序(BE)
// x86 CPU 使用小端序(LE)
// 发送网络数据
const netBuf = Buffer.alloc(4);
netBuf.writeInt32BE(value, 0); // 网络字节序
// 本地处理
const localBuf = Buffer.alloc(4);
localBuf.writeInt32LE(value, 0); // 本地字节序
3. 避免内存泄漏
// ❌ 不要保留对大 Buffer 的引用
let cache;
function process() {
const bigBuf = Buffer.alloc(1024 * 1024); // 1MB
cache = bigBuf; // 内存泄漏
}
// ✅ 只保留需要的数据
function processGood() {
const bigBuf = Buffer.alloc(1024 * 1024);
const needed = Buffer.from(bigBuf.slice(0, 100));
cache = needed; // 只保留 100 字节
}
4. 使用正确的方法复制
const src = Buffer.from('Hello');
// ❌ 创建视图,不是副本
const view = src.slice(0, 3);
// ✅ 创建独立副本
const copy = Buffer.from(src);
const copy2 = Buffer.alloc(src.length);
src.copy(copy2);
小结
本章我们学习了:
- Buffer 基本概念:什么是 Buffer、为什么需要 Buffer
- 创建 Buffer:
from、alloc、allocUnsafe - 编码转换:UTF-8、Base64、Hex 等编码
- Buffer 操作:读写、切片、复制、拼接、比较、查找
- 数字读写:整数、浮点数、BigInt 的读写方法
- Buffer 与 TypedArray:互相转换和共享内存
- 实用函数:
byteLength、fill、迭代器 - 实战示例:Base64 编码、二进制协议、文件哈希
练习
- 编写函数将字符串转换为 Base64 编码并解码回来
- 实现一个简单的二进制消息协议
- 使用 Buffer 读取 BMP 图片的宽度和高度
- 实现两个 Buffer 的异或操作
- 编写函数检测文件是否为有效的 PNG 图片