Node.js 缓冲区 Buffer

在 Node.js 中,Buffer 类用于直接处理二进制数据。由于 JavaScript 最初设计用于处理字符串而非二进制数据,因此在 Node.js 中引入了 Buffer 来满足文件 I/O、网络通信等场景的需求。Buffer 在全局作用域内可用,无需 require()。本章将深入讲解 Buffer 的创建、操作、编码转换及最佳实践。

1. 什么是 Buffer?

BufferUint8Array 的子类,用于表示固定长度的字节序列。它专门用于在 Node.js 环境中处理二进制数据,例如从文件读取的内容、网络包等。Buffer 的大小在创建时就确定,且无法调整。

在引入 Buffer 之前,Node.js 通过字符串处理二进制数据,效率低下且容易出错。Buffer 提供了高效的内存分配和操作方式,直接与 V8 堆内存交互。

2. 创建 Buffer

Node.js 提供了多种创建 Buffer 的方式。推荐使用 Buffer.alloc()Buffer.from() 等方法,避免使用 new Buffer() 构造函数(已弃用)。

2.1 Buffer.alloc(size[, fill[, encoding]])

创建一个指定大小的 Buffer,默认填充为 0。可以指定填充内容和编码。

const buf1 = Buffer.alloc(10); // 长度为 10 的零填充缓冲区
console.log(buf1); // 

const buf2 = Buffer.alloc(5, 'a'); // 用 'a' 的 ASCII 填充
console.log(buf2); // 

2.2 Buffer.allocUnsafe(size)

创建一个指定大小的 Buffer,但不会初始化内存,因此速度更快,但可能包含旧数据(敏感信息)。使用后应立即填充数据,或仅用于后续写入。

const buf3 = Buffer.allocUnsafe(10);
console.log(buf3); // 可能包含随机数据
buf3.fill(0); // 立即覆盖
安全警告: allocUnsafe 可能泄露内存中的敏感数据,除非确定会立即覆盖所有字节,否则应使用 alloc

2.3 Buffer.from()

从现有数据创建 Buffer。支持多种参数类型:

// 从字符串创建(可指定编码,默认 utf8)
const buf4 = Buffer.from('Hello Node.js', 'utf8');
console.log(buf4); // 

// 从数组创建(数组元素为字节值)
const buf5 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
console.log(buf5.toString()); // 'Hello'

// 从 Buffer 复制(新 Buffer 共享原 Buffer 的内存?不,是副本)
const buf6 = Buffer.from(buf4);
console.log(buf6.equals(buf4)); // true,但它们是独立的

3. Buffer 的基本操作

3.1 写入数据:write

buf.write(string[, offset[, length]][, encoding]) 将字符串写入 Buffer 的指定位置。

const buf = Buffer.alloc(20);
const bytesWritten = buf.write('Hello', 0, 'utf8');
console.log('写入字节数:', bytesWritten); // 5
console.log(buf.toString('utf8', 0, bytesWritten)); // 'Hello'

3.2 读取数据:toString、toJSON

const buf = Buffer.from('Node.js 教程', 'utf8');
console.log(buf.toString('utf8')); // 'Node.js 教程'
console.log(buf.toString('hex')); // 十六进制表示
console.log(buf.toJSON()); // { type: 'Buffer', data: [ 78, 111, 100, ... ] }

3.3 单个字节的读写

可以通过数组下标方式读写指定索引的字节(0-255)。

const buf = Buffer.alloc(5);
buf[0] = 72; // 'H'
buf[1] = 101; // 'e'
buf[2] = 108; // 'l'
buf[3] = 108; // 'l'
buf[4] = 111; // 'o'
console.log(buf.toString()); // 'Hello'
console.log(buf[0]); // 72

3.4 获取 Buffer 长度

const buf = Buffer.from('Hello');
console.log(buf.length); // 5(字节数,不是字符数)

4. Buffer 的常用方法

4.1 Buffer.concat(list[, totalLength])

将多个 Buffer 拼接成一个新 Buffer。

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

4.2 buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]])

比较两个 Buffer 的内容。常用于排序。

const buf1 = Buffer.from('ABC');
const buf2 = Buffer.from('ABD');
console.log(buf1.compare(buf2)); // -1(表示 buf1 < buf2)

4.3 buf.equals(otherBuffer)

判断两个 Buffer 内容是否完全相同。

const buf1 = Buffer.from('Hello');
const buf2 = Buffer.from('Hello');
const buf3 = Buffer.from('World');
console.log(buf1.equals(buf2)); // true
console.log(buf1.equals(buf3)); // false

4.4 buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

将一个 Buffer 的内容复制到另一个 Buffer。

const buf1 = Buffer.from('Hello World');
const buf2 = Buffer.alloc(5);
buf1.copy(buf2, 0, 0, 5);
console.log(buf2.toString()); // 'Hello'

4.5 buf.slice([start[, end]])

返回一个新的 Buffer,与原 Buffer 共享内存(即修改会影响原 Buffer)。

const buf1 = Buffer.from('Hello World');
const buf2 = buf1.slice(0, 5);
buf2[0] = 72; // 改为 'H' 实际上没变,因为本来就是 'H'
buf2[1] = 97; // 改为 'a'
console.log(buf1.toString()); // 'Hallo World'(因为共享内存)

4.6 buf.indexOf(value[, byteOffset][, encoding])

查找指定值在 Buffer 中第一次出现的位置。

const buf = Buffer.from('Hello World');
console.log(buf.indexOf('World')); // 6

5. 编码与解码

Buffer 支持多种字符编码,常用的有:

  • 'utf8':多字节编码,支持所有 Unicode 字符。
  • 'ascii':7 位 ASCII 编码。
  • 'hex':将每个字节编码为两个十六进制字符。
  • 'base64':Base64 编码。
  • 'latin1''binary':一种将 Buffer 编码成单字节字符串的方式。
const buf = Buffer.from('Hello', 'utf8');
console.log(buf.toString('hex')); // 48656c6c6f
console.log(buf.toString('base64')); // SGVsbG8=

6. Buffer 与 JSON

Buffer 实例的 toJSON() 返回一个包含类型和数据的对象,在 JSON 序列化时自动调用。

const buf = Buffer.from('Hello');
const json = JSON.stringify(buf);
console.log(json); // {"type":"Buffer","data":[72,101,108,108,111]}
// 还原
const parsed = JSON.parse(json);
const newBuf = Buffer.from(parsed.data);
console.log(newBuf.toString()); // 'Hello'

7. Buffer 与 TypedArray

BufferUint8Array 的子类,因此可以与其他 TypedArray 和 DataView 互操作。但需要注意,Buffer 分配的内存不在 V8 堆中,而 TypedArray 分配在堆内。

const buf = Buffer.from([1, 2, 3, 4]);
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
console.log(uint8[0]); // 1

8. 实际应用示例

8.1 文件读取中的二进制处理

const fs = require('fs');
fs.readFile('image.png', (err, data) => {
  if (err) throw err;
  // data 是一个 Buffer
  console.log('PNG 文件头:', data.slice(0, 8).toString('hex'));
});

8.2 网络传输中的 Base64 编码

const http = require('http');
const server = http.createServer((req, res) => {
  const buf = Buffer.from('Hello World');
  const base64 = buf.toString('base64');
  res.end(base64);
});
server.listen(3000);

8.3 图像处理(简单示例)

// 假设有一个 24-bit RGB 图像数据,我们想把所有红色分量设为 0
function removeRed(buffer) {
  for (let i = 0; i < buffer.length; i += 3) {
    buffer[i] = 0; // 红色字节(假设顺序为 RGB)
  }
  return buffer;
}

9. 性能与注意事项

  • 内存分配Buffer.allocUnsafe()alloc 快,但要注意安全性。
  • 内存泄漏:使用后应允许垃圾回收,避免全局引用大 Buffer。
  • 编码陷阱:不同编码转换可能导致数据损坏,确保源编码正确。
  • 共享内存slice 返回的 Buffer 与原 Buffer 共享内存,修改会影响原 Buffer。
  • 大小限制:单个 Buffer 最大容量取决于平台,通常为几GB。
最佳实践:
  • 优先使用 Buffer.from()Buffer.alloc() 创建 Buffer。
  • 处理大量数据时使用流,而非一次性加载到 Buffer。
  • 明确指定编码,避免默认行为带来意外。

小结

Buffer 是 Node.js 处理二进制数据的基石。本章介绍了 Buffer 的创建、读写、编码转换、常用方法及实际应用场景。熟练掌握 Buffer 对于深入使用 Node.js 进行文件操作、网络通信、加密解密等至关重要。下一章我们将探讨 zlib 模块,学习如何使用 Buffer 进行数据压缩与解压。