buffer 缓冲区
Buffer是NodeJS中用于处理二进制数据的模块。在JavaScript语言中,字符串是不可变的且使用UTF-16编码存储,这对于处理原始二进制数据如文件I/O、网络通信等场景很不方便。Buffer类则提供了一种高效操作二进制数据的方式。
Buffer的基本概念
Buffer是NodeJS中的全局对象,无需导入即可直接使用,它本质上是一块内存区域,用于存储原始二进制数据。Buffer的大小在创建时确定,之后不可更改。
// Buffer是全局对象,可以直接使用
console.log(Buffer); // [Function: Buffer]
// 也可以从buffer模块显式导入
import { Buffer } from "node:buffer";
此外还需要了解的一点是,Buffer的内存分配发生在V8堆外,因此不会使用V8的垃圾回收机制。不过虽然不在V8堆中,但这段内存是在NodeJS层面使用引用计数机制管理的,因此它仍会自动回收,无需像C/C++一样手动释放内存。Buffer频繁的创建和销毁具有一定的性能开销,因此对小于4KB的Buffer,NodeJS会使用预分配的内存池以提高性能。
// 小Buffer共享内存池
const buf1 = Buffer.from("a");
const buf2 = Buffer.from("b");
// 可以通过buffer属性查看底层ArrayBuffer
console.log(buf1.buffer === buf2.buffer); // 可能为true(共享池)
// allocUnsafeSlow()强制不使用内存池
const buf3 = Buffer.allocUnsafeSlow(100);
创建Buffer
NodeJS提供了多种创建Buffer的方式,不同方式适用于不同场景。
Buffer.alloc()
Buffer.alloc()用于创建指定大小的Buffer,并以零值填充。这是创建Buffer最安全的方式,因为它会清空内存中的旧数据。
// 创建一个长度为10的Buffer,用0填充
const buf1 = Buffer.alloc(10);
console.log(buf1); // <Buffer 00 00 00 00 00 00 00 00 00 00>
// 创建一个长度为10的Buffer,用指定值填充
const buf2 = Buffer.alloc(10, 0xff);
console.log(buf2); // <Buffer ff ff ff ff ff ff ff ff ff ff>
// 用字符串填充
const buf3 = Buffer.alloc(10, "a");
console.log(buf3); // <Buffer 61 61 61 61 61 61 61 61 61 61>
Buffer.allocUnsafe()
Buffer.allocUnsafe()创建指定大小的Buffer,但不会清空内存,因此可能包含旧数据。这种方式速度更快,但需要开发者自行确保后续会完全覆盖Buffer内容。
// 创建一个长度为10的未初始化Buffer
const buf = Buffer.allocUnsafe(10);
console.log(buf); // 可能包含任意旧数据
// 使用fill()方法手动清空
buf.fill(0);
console.log(buf); // <Buffer 00 00 00 00 00 00 00 00 00 00>
Buffer.from()
Buffer.from()可以从多种数据源创建Buffer,这是最常用的创建方式。
// 从字符串创建
const buf1 = Buffer.from("Hello, World!");
console.log(buf1); // <Buffer 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21>
console.log(buf1.toString()); // Hello, World!
// 从字符串创建,指定编码
const buf2 = Buffer.from("你好", "utf8");
console.log(buf2); // <Buffer e4 bd a0 e5 a5 bd>
// 从数组创建
const buf3 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
console.log(buf3.toString()); // Hello
// 从另一个Buffer创建(拷贝)
const buf4 = Buffer.from(buf1);
console.log(buf4.toString()); // Hello, World!
// 从ArrayBuffer创建
const arrayBuffer = new ArrayBuffer(8);
const buf5 = Buffer.from(arrayBuffer);
console.log(buf5.length); // 8
Buffer的读写操作
索引访问
Buffer可以像数组一样通过索引访问和修改单个字节。
const buf = Buffer.alloc(4);
// 写入单个字节
buf[0] = 0x48; // H
buf[1] = 0x69; // i
buf[2] = 0x21; // !
buf[3] = 0x00;
console.log(buf[0]); // 72 (0x48的十进制)
console.log(buf.toString()); // Hi!
write()方法
write()方法用于向Buffer写入字符串。
const buf = Buffer.alloc(20);
// 写入字符串,返回写入的字节数
const bytesWritten = buf.write("Hello");
console.log(bytesWritten); // 5
console.log(buf.toString()); // Hello
// 指定偏移量写入
buf.write(" World", 5);
console.log(buf.toString()); // Hello World
// 指定长度和编码
buf.write("你好", 11, 6, "utf8");
console.log(buf.toString()); // Hello World你好
读写整数
Buffer提供了多种读写整数的方法,支持不同的字节序和整数大小。
const buf = Buffer.alloc(8);
// 写入无符号整数
buf.writeUInt8(255, 0); // 1字节
buf.writeUInt16BE(65535, 1); // 2字节,大端序
buf.writeUInt16LE(65535, 3); // 2字节,小端序
buf.writeUInt32BE(4294967295, 4); // 4字节,大端序(会溢出)
console.log(buf);
// 读取整数
const buf2 = Buffer.from([0x01, 0x02, 0x03, 0x04]);
console.log(buf2.readUInt8(0)); // 1
console.log(buf2.readUInt16BE(0)); // 258 (0x0102)
console.log(buf2.readUInt16LE(0)); // 513 (0x0201)
console.log(buf2.readUInt32BE(0)); // 16909060 (0x01020304)
读写浮点数
const buf = Buffer.alloc(8);
// 写入浮点数
buf.writeFloatBE(3.14, 0); // 4字节单精度
buf.writeDoubleBE(3.14, 0); // 8字节双精度
// 读取浮点数
console.log(buf.readFloatBE(0));
console.log(buf.readDoubleBE(0));
Buffer与字符串转换
Buffer转字符串
toString()方法将Buffer转换为字符串,支持多种编码。
const buf = Buffer.from("Hello, 世界!");
// 默认使用utf8编码
console.log(buf.toString()); // Hello, 世界!
// 指定编码
console.log(buf.toString("utf8")); // Hello, 世界!
console.log(buf.toString("hex")); // 48656c6c6f2c20e4b896e7958c21
console.log(buf.toString("base64")); // SGVsbG8sIOS4lueVjCE=
// 指定起始和结束位置
console.log(buf.toString("utf8", 0, 5)); // Hello
字符串转Buffer
NodeJS的Buffer支持从以下编码格式的字符串创建。
// utf8 - 多字节编码的Unicode字符,默认编码
const buf1 = Buffer.from("你好", "utf8");
// utf16le / ucs2 - 小端序编码的Unicode字符
const buf2 = Buffer.from("你好", "utf16le");
// latin1 / binary - 单字节编码
const buf3 = Buffer.from("hello", "latin1");
// base64 - Base64编码
const buf4 = Buffer.from("SGVsbG8=", "base64");
console.log(buf4.toString()); // Hello
// base64url - URL安全的Base64编码
const buf5 = Buffer.from("SGVsbG8", "base64url");
// hex - 十六进制编码
const buf6 = Buffer.from("48656c6c6f", "hex");
console.log(buf6.toString()); // Hello
// ascii - 7位ASCII编码
const buf7 = Buffer.from("hello", "ascii");
Buffer的常用操作
切片操作
subarray()方法返回Buffer的一个切片,注意Buffer切片与原Buffer共享内存。
const buf = Buffer.from("Hello, World!");
// 获取切片(共享内存)
const slice = buf.subarray(0, 5);
console.log(slice.toString()); // Hello
// 修改切片会影响原Buffer
slice[0] = 0x68; // 小写h
console.log(buf.toString()); // hello, World!
// 如果需要独立副本,使用Buffer.from()
const copy = Buffer.from(buf.subarray(0, 5));
copy[0] = 0x48;
console.log(buf.toString()); // hello, World! (不受影响)
拷贝操作
copy()方法将Buffer的内容拷贝到另一个Buffer。
const src = Buffer.from("Hello");
const dst = Buffer.alloc(10);
// 将src拷贝到dst
src.copy(dst);
console.log(dst.toString()); // Hello
// 指定目标起始位置
const dst2 = Buffer.alloc(10);
src.copy(dst2, 5);
console.log(dst2.toString()); // \x00\x00\x00\x00\x00Hello
// 指定源范围
const dst3 = Buffer.alloc(10);
src.copy(dst3, 0, 1, 4); // 拷贝src[1:4]到dst3
console.log(dst3.toString()); // ell
拼接操作
Buffer.concat()用于拼接多个Buffer。
const buf1 = Buffer.from("Hello, ");
const buf2 = Buffer.from("World");
const buf3 = Buffer.from("!");
// 拼接Buffer数组
const combined = Buffer.concat([buf1, buf2, buf3]);
console.log(combined.toString()); // Hello, World!
// 指定总长度(可用于截断)
const truncated = Buffer.concat([buf1, buf2, buf3], 12);
console.log(truncated.toString()); // Hello, World
比较操作
Buffer之间可以进行比较,其中相等比较最为常用。
const buf1 = Buffer.from("ABC");
const buf2 = Buffer.from("ABD");
const buf3 = Buffer.from("ABC");
// compare()返回-1、0、1
console.log(buf1.compare(buf2)); // -1 (buf1 < buf2)
console.log(buf1.compare(buf3)); // 0 (buf1 === buf3)
console.log(buf2.compare(buf1)); // 1 (buf2 > buf1)
// equals()判断是否相等
console.log(buf1.equals(buf3)); // true
console.log(buf1.equals(buf2)); // false
// Buffer.compare()静态方法
console.log(Buffer.compare(buf1, buf2)); // -1
查找操作
下面例子演示如何在Buffer中查找内容。
const buf = Buffer.from("Hello, World!");
// indexOf()查找首次出现位置
console.log(buf.indexOf("o")); // 4
console.log(buf.indexOf("o", 5)); // 8(从索引5开始查找)
console.log(buf.indexOf(Buffer.from("World"))); // 7
console.log(buf.indexOf("xyz")); // -1(未找到)
// lastIndexOf()查找最后出现位置
console.log(buf.lastIndexOf("o")); // 8
// includes()判断是否包含
console.log(buf.includes("World")); // true
console.log(buf.includes("xyz")); // false
填充操作
下面代码演示如何填充Buffer。
const buf = Buffer.alloc(10);
// 填充单个值
buf.fill(1);
console.log(buf); // <Buffer 01 01 01 01 01 01 01 01 01 01>
// 填充字符串
buf.fill("ab");
console.log(buf.toString()); // ababababab
// 指定填充范围
buf.fill(0);
buf.fill("x", 2, 5);
console.log(buf.toString()); // \x00\x00xxx\x00\x00\x00\x00\x00
迭代Buffer
Buffer支持多种迭代方式。
const buf = Buffer.from("Hello");
// for...of遍历字节值
for (const byte of buf) {
console.log(byte); // 72, 101, 108, 108, 111
}
// entries()获取索引和值
for (const [index, byte] of buf.entries()) {
console.log(index, byte);
}
// keys()获取索引
for (const index of buf.keys()) {
console.log(index); // 0, 1, 2, 3, 4
}
// values()获取值
for (const byte of buf.values()) {
console.log(byte); // 72, 101, 108, 108, 111
}
Buffer与TypedArray
Buffer是Uint8Array的子类,它可以与各种TypedArray进行互操作。
// Buffer继承自Uint8Array
const buf = Buffer.from([1, 2, 3, 4]);
console.log(buf instanceof Uint8Array); // true
// 从TypedArray创建Buffer
const uint16 = new Uint16Array([256, 512]);
const buf2 = Buffer.from(uint16.buffer);
console.log(buf2); // <Buffer 00 01 00 02>
// Buffer可以使用TypedArray的方法
const buf3 = Buffer.from([3, 1, 4, 1, 5, 9, 2, 6]);
const sorted = buf3.sort();
console.log(sorted); // <Buffer 01 01 02 03 04 05 06 09>
// 使用map、filter等方法(注意返回的是Uint8Array)
const doubled = buf.map((x) => x * 2);
console.log(doubled); // Uint8Array(4) [ 2, 4, 6, 8 ]
console.log(Buffer.from(doubled)); // <Buffer 02 04 06 08>