强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 9 章 · Buffer 与二进制数据

第 9 章 · Buffer 与二进制数据

9.1 什么是 Buffer

Buffer 是 Node.js 中用于处理二进制数据的类。由于 JavaScript 原生不支持二进制数据操作,Node.js 引入了 Buffer 来处理文件 I/O、网络协议、加密等场景。

// Buffer 在全局可用,无需 require
console.log(typeof Buffer); // 'function'

Buffer 的特点

特性说明
固定长度创建后大小不可改变
内存分配直接在 V8 堆外分配内存
编码支持支持 UTF-8、ASCII、Base64、Hex 等
与 Stream 配合流的数据块就是 Buffer

9.2 创建 Buffer

// ⚠️ new Buffer() 已废弃,不要使用!

// ✅ Buffer.alloc(size, fill, encoding) — 安全创建
const buf1 = Buffer.alloc(10);           // 10 字节,填充 0
const buf2 = Buffer.alloc(10, 0x61);     // 10 字节,填充 'a'
const buf3 = Buffer.alloc(10, 'hello');  // 10 字节,用 'hello' 填充

// ✅ Buffer.allocUnsafe(size) — 不安全但快
const buf4 = Buffer.allocUnsafe(10);     // 可能包含旧数据!
buf4.fill(0); // 应立即填充

// ✅ Buffer.from() — 从已有数据创建
const buf5 = Buffer.from([1, 2, 3, 4, 5]);
const buf6 = Buffer.from('Hello, World!');
const buf7 = Buffer.from('你好', 'utf8');
const buf8 = Buffer.from('48656c6c6f', 'hex');  // 'Hello'
const buf9 = Buffer.from('SGVsbG8=', 'base64'); // 'Hello'

// ✅ Buffer.allocUnsafeSlow(size) — 不使用预分配的内存池
const buf10 = Buffer.allocUnsafeSlow(10);

alloc vs allocUnsafe

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'); // 较快

// 结论:性能敏感场景使用 allocUnsafe + fill
// 安全敏感场景使用 alloc

9.3 Buffer 操作

基本操作

const buf = Buffer.from('Hello, Node.js!');

// 长度
console.log(buf.length); // 15

// 读取字节
console.log(buf[0]);  // 72 (H 的 ASCII 码)

// 修改字节
buf[0] = 0x68; // 改为 'h'

// 转为字符串
console.log(buf.toString());          // 'hello, Node.js!'
console.log(buf.toString('utf8'));    // 'hello, Node.js!'
console.log(buf.toString('ascii'));   // 'hello, Node.js!'
console.log(buf.toString('hex'));     // '68656c6c6f...'
console.log(buf.toString('base64'));  // 'aGVsbG8s...'
console.log(buf.toString('utf8', 0, 5)); // 'hello' (部分解码)

// JSON 序列化
console.log(buf.toJSON());
// { type: 'Buffer', data: [104, 101, 108, 108, 111, ...] }

切片与拷贝

const original = Buffer.from('Hello, World!');

// slice — 创建视图(共享内存!)
const slice = original.subarray(0, 5);
console.log(slice.toString()); // 'Hello'

// ⚠️ 修改切片会影响原始 Buffer
slice[0] = 0x48; // 'H'
console.log(original.toString()); // 'Hello, World!' (没变,因为值相同)

// ✅ 安全拷贝
const copy = Buffer.alloc(5);
original.copy(copy, 0, 0, 5);
console.log(copy.toString()); // 'Hello'

// copy 的参数:copy(target, targetStart, sourceStart, sourceEnd)
const target = Buffer.alloc(20);
original.copy(target, 5, 0, 5);
console.log(target.toString()); // '\x00\x00\x00\x00\x00Hello...'

拼接 Buffer

const buf1 = Buffer.from('Hello');
const buf2 = Buffer.from(', ');
const buf3 = Buffer.from('World!');

// Buffer.concat — 拼接多个 Buffer
const result = Buffer.concat([buf1, buf2, buf3]);
console.log(result.toString()); // 'Hello, World!'

// 指定总长度
const result2 = Buffer.concat([buf1, buf2, buf3], 8);
console.log(result2.toString()); // 'Hello, W'

9.4 编码

const text = '你好,世界!';

// UTF-8 编码(默认)
const utf8Buf = Buffer.from(text, 'utf8');
console.log(utf8Buf.length); // 18(每个中文 3 字节)

// UTF-16LE 编码
const utf16Buf = Buffer.from(text, 'utf16le');
console.log(utf16Buf.length); // 14(每个中文 2 字节 + 标点 1 字符 = 2 字节)

// Base64 编码
const base64 = Buffer.from('Hello').toString('base64');
console.log(base64); // 'SGVsbG8='

// Base64 解码
const fromBase64 = Buffer.from('SGVsbG8=', 'base64');
console.log(fromBase64.toString()); // 'Hello'

// Hex 编码
const hex = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]).toString('hex');
console.log(hex); // '48656c6c6f'

// URL 安全的 Base64
function base64UrlEncode(buffer) {
  return buffer.toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

中文字符长度

// 字符长度 vs 字节长度
const str = '你好';
console.log(str.length);               // 2(字符数)
console.log(Buffer.byteLength(str));    // 6(字节数,UTF-8)
console.log(Buffer.byteLength(str, 'utf16le')); // 4

// 统计实际显示宽度
function getStringWidth(str) {
  let width = 0;
  for (const char of str) {
    const code = char.codePointAt(0);
    // CJK 字符宽度为 2
    if (code >= 0x4e00 && code <= 0x9fff) {
      width += 2;
    } else {
      width += 1;
    }
  }
  return width;
}

console.log(getStringWidth('Hello你好')); // 9

9.5 Buffer 与 TypedArray

// Buffer 与 Uint8Array 共享底层内存
const buf = Buffer.from([1, 2, 3, 4]);
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);

console.log(uint8); // Uint8Array [1, 2, 3, 4]
uint8[0] = 255;
console.log(buf[0]); // 255(共享内存!)

// Uint8Array → Buffer
const arr = new Uint8Array([10, 20, 30]);
const bufFromArr = Buffer.from(arr);

// ArrayBuffer → Buffer
const ab = new ArrayBuffer(8);
const bufFromAB = Buffer.from(ab);

// 注意:Buffer.from(arrayBuffer) 共享内存
// Buffer.from(arrayBuffer, byteOffset, length) 也共享内存

9.6 实用场景

计算文件哈希

const crypto = require('crypto');
const fs = require('fs');

// 同步计算
function hashFile(filePath, algorithm = 'sha256') {
  const data = fs.readFileSync(filePath);
  return crypto.createHash(algorithm).update(data).digest('hex');
}

console.log(hashFile('package.json'));

// 流式计算(大文件)
async function hashFileStream(filePath) {
  return new Promise((resolve, reject) => {
    const hash = crypto.createHash('sha256');
    fs.createReadStream(filePath)
      .on('data', (chunk) => hash.update(chunk))
      .on('end', () => resolve(hash.digest('hex')))
      .on('error', reject);
  });
}

Base64 图片

const fs = require('fs');

// 图片转 Base64
function imageToBase64(filePath) {
  const data = fs.readFileSync(filePath);
  const ext = path.extname(filePath).slice(1);
  return `data:image/${ext};base64,${data.toString('base64')}`;
}

// Base64 转图片
function base64ToImage(base64String, outputPath) {
  const base64Data = base64String.replace(/^data:image\/\w+;base64,/, '');
  fs.writeFileSync(outputPath, Buffer.from(base64Data, 'base64'));
}

二进制协议解析

// 解析自定义二进制协议
function parsePacket(buffer) {
  let offset = 0;
  
  // 协议格式:[魔数 2B][版本 1B][类型 1B][长度 4B][数据 NB]
  const magic = buffer.readUInt16BE(offset); offset += 2;
  const version = buffer.readUInt8(offset); offset += 1;
  const type = buffer.readUInt8(offset); offset += 1;
  const length = buffer.readUInt32BE(offset); offset += 4;
  const data = buffer.subarray(offset, offset + length);

  return { magic, version, type, length, data };
}

// 构建数据包
function buildPacket(type, data) {
  const header = Buffer.alloc(8);
  header.writeUInt16BE(0xABCD, 0); // 魔数
  header.writeUInt8(1, 2);          // 版本
  header.writeUInt8(type, 3);       // 类型
  header.writeUInt32BE(data.length, 4); // 长度
  return Buffer.concat([header, data]);
}

注意事项

⚠️ 永远不要使用 new Buffer():已废弃,存在安全隐患。使用 Buffer.from()Buffer.alloc()

⚠️ subarray() 共享内存:对切片的修改会影响原始 Buffer。需要独立副本时使用 Buffer.from()

⚠️ allocUnsafe 可能泄露数据:未初始化的 Buffer 可能包含敏感信息,生产环境应使用 alloc()

⚠️ Buffer 大小限制:单个 Buffer 最大 2GB - 1 字节(32 位系统为 1GB - 1)。

业务场景

  1. 文件哈希校验:计算 SHA-256 用于文件完整性验证
  2. 图片处理:读取图片元数据、格式转换
  3. 网络协议:实现 TCP/WebSocket 自定义协议
  4. 加密解密:AES/RSA 加密操作的基础数据类型

扩展阅读


上一章第 8 章 · 流(Streams) 下一章第 10 章 · 文件系统 — fs 模块、文件读写、目录操作和文件监听。