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>
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。