Skip to content

Buffer 与二进制数据处理

一句话概述:Buffer 原理、编码转换、性能优化、TypedArray 关系——掌握 Node.js 处理二进制数据的核心 API

什么是 Buffer?

定义:Buffer 是 Node.js 中用于处理二进制数据的类,类似于整数数组,但专门用于表示固定长度的字节序列。

涉及场景

  • 文件操作:读写二进制文件
  • 网络传输:TCP/UDP 数据包
  • 加密解密:crypto 模块
  • 图像处理:图片编解码

作用

  1. 高效处理二进制数据
  2. 字符串编码转换
  3. 与 Stream 配合使用

一、Buffer 基础

创建 Buffer

Node.js 提供三种创建 Buffer 的方法:

1. Buffer.from() - 从现有数据创建

javascript
// 从字符串创建
const buf1 = Buffer.from('Hello World', 'utf8');

// 从数组创建
const buf2 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);

// 从另一个 Buffer 创建(拷贝)
const buf3 = Buffer.from(buf1);

// 从 ArrayBuffer 创建
const arr = new Uint8Array(2);
const buf4 = Buffer.from(arr.buffer);

2. Buffer.alloc() - 创建已初始化的 Buffer

javascript
// 创建 10 字节的 Buffer,填充 0
const buf = Buffer.alloc(10);

// 创建 10 字节的 Buffer,填充 1
const buf2 = Buffer.alloc(10, 1);

// 创建 10 字节的 Buffer,填充 'a'
const buf3 = Buffer.alloc(10, 'a');

3. Buffer.allocUnsafe() - 创建未初始化的 Buffer(性能更高)

javascript
// 创建 10 字节的 Buffer,内容未初始化(可能包含旧数据)
const buf = Buffer.allocUnsafe(10);

// 必须手动填充数据
buf.fill(0);

三种方法的区别

  • Buffer.from(): 从现有数据创建,最常用
  • Buffer.alloc(): 创建并初始化为 0,安全但稍慢
  • Buffer.allocUnsafe(): 不初始化,速度最快但可能泄露旧数据

Buffer 与字符串转换

Buffer 转字符串

javascript
const buf = Buffer.from('Hello 世界', 'utf8');

// 转为 UTF-8 字符串(默认)
console.log(buf.toString()); // 'Hello 世界'

// 指定编码
console.log(buf.toString('hex')); // '48656c6c6f20e4b896e7958c'
console.log(buf.toString('base64')); // 'SGVsbG8g5LiW55WM'

// 指定范围
console.log(buf.toString('utf8', 0, 5)); // 'Hello'

支持的编码类型

  • utf8: 默认编码,多字节 Unicode 字符
  • utf16le: 2 或 4 字节,小端编码的 Unicode 字符
  • latin1: 单字节编码(ISO-8859-1)
  • base64: Base64 编码
  • hex: 十六进制编码
  • ascii: 7 位 ASCII 编码
  • binary: latin1 的别名

中文字符处理

javascript
// 一个中文字符在 UTF-8 中占 3 字节
const buf = Buffer.from('你好');
console.log(buf.length); // 6

// 截断可能导致乱码
const buf2 = Buffer.from('你好世界');
console.log(buf2.toString('utf8', 0, 4)); // '你�' (乱码)

// 使用 StringDecoder 避免乱码
const { StringDecoder } = require('string_decoder');
const decoder = new StringDecoder('utf8');
console.log(decoder.write(buf2.slice(0, 4))); // '你' (正确处理)

二、Buffer 操作

读写操作

读取数据

javascript
const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]);

// 读取无符号整数
console.log(buf.readUInt8(0)); // 18 (0x12)
console.log(buf.readUInt16BE(0)); // 4660 (0x1234)
console.log(buf.readUInt32BE(0)); // 305419896 (0x12345678)

// 读取有符号整数
const buf2 = Buffer.from([0xff, 0xff]);
console.log(buf2.readInt16BE(0)); // -1

// 读取浮点数
const buf3 = Buffer.allocUnsafe(4);
buf3.writeFloatBE(3.14, 0);
console.log(buf3.readFloatBE(0)); // 3.14

写入数据

javascript
const buf = Buffer.allocUnsafe(6);

// 写入整数
buf.writeUInt8(0x12, 0);
buf.writeUInt16BE(0x3456, 1);
buf.writeUInt8(0x78, 3);

// 写入字符串
buf.write('AB', 4, 'utf8');

console.log(buf); // <Buffer 12 34 56 78 41 42>

slice 和 copy

javascript
const buf1 = Buffer.from('Hello World');

// slice 创建视图(共享内存)
const buf2 = buf1.slice(0, 5);
buf2[0] = 0x48; // 修改 buf2 会影响 buf1
console.log(buf1.toString()); // 'Hello World'

// subarray 是 slice 的别名(推荐使用)
const buf3 = buf1.subarray(0, 5);

// copy 创建副本(独立内存)
const buf4 = Buffer.allocUnsafe(5);
buf1.copy(buf4, 0, 0, 5);
buf4[0] = 0x41; // 修改 buf4 不影响 buf1

Buffer 拼接

使用 Buffer.concat()

javascript
const buf1 = Buffer.from('Hello ');
const buf2 = Buffer.from('World');

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

// 指定总长度(性能优化)
const buf4 = Buffer.concat([buf1, buf2], 11);

性能陷阱:避免 += 拼接

javascript
// ❌ 错误:每次都创建新 Buffer,性能极差
let result = Buffer.alloc(0);
for (let i = 0; i < 1000; i++) {
  result = Buffer.concat([result, Buffer.from('data')]);
}

// ✅ 正确:先收集,最后一次性拼接
const buffers = [];
for (let i = 0; i < 1000; i++) {
  buffers.push(Buffer.from('data'));
}
const result = Buffer.concat(buffers);

处理流式数据

javascript
const chunks = [];
let totalLength = 0;

readableStream.on('data', (chunk) => {
  chunks.push(chunk);
  totalLength += chunk.length;
});

readableStream.on('end', () => {
  const result = Buffer.concat(chunks, totalLength);
  console.log(result.toString());
});

三、编码与解码

支持的编码类型

常用编码对比

编码用途特点
utf8文本存储、网络传输变长编码,兼容 ASCII,默认编码
utf16leWindows 文件系统固定 2/4 字节,小端序
base64邮件、URL、数据嵌入文本安全,体积增加 33%
hex调试、哈希值显示可读性好,体积翻倍
latin1二进制数据单字节,与 Buffer 一一对应
ascii纯英文文本7 位编码,不支持中文

编码转换示例

javascript
const text = 'Hello 世界';

// UTF-8 编码(默认)
const utf8Buf = Buffer.from(text, 'utf8');
console.log(utf8Buf.length); // 12 字节

// Base64 编码
const base64 = utf8Buf.toString('base64');
console.log(base64); // 'SGVsbG8g5LiW55WM'

// Hex 编码
const hex = utf8Buf.toString('hex');
console.log(hex); // '48656c6c6f20e4b896e7958c'

// 解码回原文
console.log(Buffer.from(base64, 'base64').toString()); // 'Hello 世界'
console.log(Buffer.from(hex, 'hex').toString()); // 'Hello 世界'

Base64 编解码

图片转 Base64

javascript
const fs = require('fs');

// 读取图片并转为 Base64
const imageBuffer = fs.readFileSync('image.png');
const base64Image = imageBuffer.toString('base64');

// 用于 HTML img 标签
const dataUrl = `data:image/png;base64,${base64Image}`;

// Base64 转回图片
const imageData = base64Image.replace(/^data:image\/\w+;base64,/, '');
const buffer = Buffer.from(imageData, 'base64');
fs.writeFileSync('output.png', buffer);

性能对比

javascript
const data = Buffer.alloc(1024 * 1024); // 1MB

console.time('base64 encode');
const base64 = data.toString('base64');
console.timeEnd('base64 encode'); // ~5ms

console.time('base64 decode');
const decoded = Buffer.from(base64, 'base64');
console.timeEnd('base64 decode'); // ~3ms

console.time('hex encode');
const hex = data.toString('hex');
console.timeEnd('hex encode'); // ~8ms

Base64 的优缺点

  • ✅ 文本安全,可嵌入 JSON/XML
  • ✅ 无需额外文件,减少 HTTP 请求
  • ❌ 体积增加 33%
  • ❌ 不适合大文件(>100KB)

四、Buffer 与 TypedArray

关系与区别

Buffer 是 Uint8Array 的子类

javascript
const buf = Buffer.from([1, 2, 3]);

console.log(buf instanceof Uint8Array); // true
console.log(buf instanceof Buffer); // true

// Buffer 继承了 TypedArray 的所有方法
console.log(buf.map(x => x * 2)); // <Buffer 02 04 06>

共享 ArrayBuffer

javascript
// Buffer 底层使用 ArrayBuffer 存储数据
const buf = Buffer.from([1, 2, 3, 4]);
console.log(buf.buffer); // ArrayBuffer { byteLength: 8192 }

// 注意:Buffer 可能使用 Buffer Pool,buffer 属性返回的是整个池
console.log(buf.byteOffset); // 池中的偏移量
console.log(buf.byteLength); // 4(实际使用的字节数)

// 获取独立的 ArrayBuffer
const arrayBuffer = buf.buffer.slice(
  buf.byteOffset,
  buf.byteOffset + buf.byteLength
);

Buffer vs TypedArray

特性BufferTypedArray
环境Node.js 专有浏览器和 Node.js
编码支持多种字符编码仅二进制
方法write/read/toStringset/subarray
性能针对 I/O 优化通用数组操作

互操作

ArrayBuffer 转 Buffer

javascript
const arrayBuffer = new ArrayBuffer(8);
const uint8Array = new Uint8Array(arrayBuffer);
uint8Array[0] = 255;

// 从 ArrayBuffer 创建 Buffer(共享内存)
const buf1 = Buffer.from(arrayBuffer);
console.log(buf1[0]); // 255

// 从 TypedArray 创建 Buffer(拷贝数据)
const buf2 = Buffer.from(uint8Array);
uint8Array[0] = 0;
console.log(buf2[0]); // 255(不受影响)

Buffer 转 TypedArray

javascript
const buf = Buffer.from([1, 2, 3, 4]);

// 方法 1:直接使用(Buffer 本身就是 Uint8Array)
const uint8 = buf;

// 方法 2:创建新的 TypedArray 视图
const uint16 = new Uint16Array(
  buf.buffer,
  buf.byteOffset,
  buf.byteLength / 2
);
console.log(uint16); // Uint16Array [ 513, 1027 ]

// 方法 3:拷贝到新的 ArrayBuffer
const newBuf = new Uint8Array(buf);

跨环境数据传输

javascript
// Node.js 发送数据
const buf = Buffer.from('Hello');
const arrayBuffer = buf.buffer.slice(
  buf.byteOffset,
  buf.byteOffset + buf.byteLength
);
// 发送 arrayBuffer 到浏览器

// 浏览器接收数据
const uint8Array = new Uint8Array(arrayBuffer);
const text = new TextDecoder().decode(uint8Array);
console.log(text); // 'Hello'

五、性能优化

Buffer Pool

Buffer Pool 机制

Node.js 使用 8KB 的预分配内存池来优化小 Buffer 的创建性能。

javascript
// 小于 4KB 的 Buffer 会从池中分配
const buf1 = Buffer.allocUnsafe(100); // 从池中分配
const buf2 = Buffer.allocUnsafe(100); // 从同一个池中分配

// 大于等于 4KB 的 Buffer 独立分配
const buf3 = Buffer.allocUnsafe(4096); // 独立分配

// 查看 Buffer 的底层 ArrayBuffer
console.log(buf1.buffer.byteLength); // 8192 (8KB 池)
console.log(buf3.buffer.byteLength); // 4096 (独立)

allocUnsafe 的性能优势

javascript
const iterations = 1000000;

// Buffer.alloc() - 安全但慢
console.time('alloc');
for (let i = 0; i < iterations; i++) {
  Buffer.alloc(100);
}
console.timeEnd('alloc'); // ~150ms

// Buffer.allocUnsafe() - 快但不安全
console.time('allocUnsafe');
for (let i = 0; i < iterations; i++) {
  Buffer.allocUnsafe(100);
}
console.timeEnd('allocUnsafe'); // ~50ms (快 3 倍)

安全隐患

javascript
// ⚠️ allocUnsafe 可能泄露旧数据
const buf1 = Buffer.allocUnsafe(10);
console.log(buf1); // <Buffer 00 00 a8 3f 00 00 00 00 ...> (随机数据)

// 必须手动初始化
buf1.fill(0);
// 或者立即写入数据
buf1.write('Hello');

// ✅ alloc 自动初始化为 0
const buf2 = Buffer.alloc(10);
console.log(buf2); // <Buffer 00 00 00 00 00 00 00 00 00 00>

使用建议

  • 性能敏感场景:使用 allocUnsafe + fill()
  • 一般场景:使用 alloc 保证安全
  • 从现有数据创建:使用 Buffer.from()

避免频繁创建

复用 Buffer

javascript
// ❌ 错误:频繁创建销毁
function processData(data) {
  const buf = Buffer.from(data); // 每次都创建新 Buffer
  // 处理 buf
  return buf.toString('base64');
}

// ✅ 正确:复用 Buffer
class DataProcessor {
  constructor() {
    this.buffer = Buffer.allocUnsafe(1024); // 预分配
  }
  
  process(data) {
    const length = this.buffer.write(data);
    return this.buffer.toString('base64', 0, length);
  }
}

预分配策略

javascript
// 场景:批量处理固定大小的数据
class BatchProcessor {
  constructor(batchSize = 100, itemSize = 256) {
    // 预分配整个批次的 Buffer
    this.buffer = Buffer.allocUnsafe(batchSize * itemSize);
    this.itemSize = itemSize;
  }
  
  processItem(index, data) {
    const offset = index * this.itemSize;
    this.buffer.write(data, offset, this.itemSize);
  }
  
  getResult() {
    return this.buffer;
  }
}

对象池模式

javascript
class BufferPool {
  constructor(size = 10, bufferSize = 1024) {
    this.pool = [];
    this.bufferSize = bufferSize;
    
    // 预创建 Buffer 池
    for (let i = 0; i < size; i++) {
      this.pool.push(Buffer.allocUnsafe(bufferSize));
    }
  }
  
  acquire() {
    return this.pool.pop() || Buffer.allocUnsafe(this.bufferSize);
  }
  
  release(buffer) {
    buffer.fill(0); // 清空数据
    this.pool.push(buffer);
  }
}

const pool = new BufferPool();
const buf = pool.acquire();
// 使用 buf
pool.release(buf);

面试高频题

1. Buffer.alloc() 和 Buffer.allocUnsafe() 的区别?

核心区别

  • Buffer.alloc(size): 创建指定大小的 Buffer,并自动填充 0,安全但较慢
  • Buffer.allocUnsafe(size): 创建指定大小的 Buffer,不初始化内存,可能包含旧数据,快但不安全

性能差异

javascript
// allocUnsafe 比 alloc 快约 2-3 倍
const buf1 = Buffer.alloc(1024);      // ~100ns
const buf2 = Buffer.allocUnsafe(1024); // ~30ns

安全隐患

javascript
const unsafe = Buffer.allocUnsafe(10);
console.log(unsafe); // 可能包含敏感数据(密码、token 等)

// 使用前必须初始化
unsafe.fill(0);

使用场景

  • alloc: 默认选择,适合大多数场景
  • allocUnsafe: 性能关键路径 + 立即写入数据的场景

Buffer Pool: 小于 4KB 的 Buffer 会从 8KB 的预分配池中分配,进一步提升性能。

2. Buffer 如何处理中文字符?

中文字符的字节占用

javascript
const buf = Buffer.from('你好世界');
console.log(buf.length); // 12 字节(每个中文 3 字节)
console.log('你好世界'.length); // 4 字符

截断导致的乱码问题

javascript
const buf = Buffer.from('你好世界');

// ❌ 直接截断会乱码
console.log(buf.toString('utf8', 0, 4)); // '你�'
console.log(buf.toString('utf8', 0, 7)); // '你好�'

// ✅ 按字符边界截断
console.log(buf.toString('utf8', 0, 6)); // '你好'

使用 StringDecoder 避免乱码

javascript
const { StringDecoder } = require('string_decoder');
const decoder = new StringDecoder('utf8');

// 处理不完整的 UTF-8 序列
const buf1 = Buffer.from([0xe4, 0xbd]); // '你' 的前 2 字节
const buf2 = Buffer.from([0xa0]); // '你' 的最后 1 字节

console.log(decoder.write(buf1)); // '' (等待完整字符)
console.log(decoder.write(buf2)); // '你' (输出完整字符)

流式处理中文

javascript
const decoder = new StringDecoder('utf8');
let result = '';

readableStream.on('data', (chunk) => {
  result += decoder.write(chunk); // 自动处理边界
});

readableStream.on('end', () => {
  result += decoder.end(); // 输出剩余字符
  console.log(result);
});

3. Buffer 和 TypedArray 的关系?

继承关系

javascript
const buf = Buffer.from([1, 2, 3]);

console.log(buf instanceof Uint8Array); // true
console.log(buf instanceof Buffer);     // true

// Buffer 是 Uint8Array 的子类
Object.getPrototypeOf(Buffer.prototype) === Uint8Array.prototype; // true

共享 ArrayBuffer

javascript
const buf = Buffer.from([1, 2, 3, 4]);

// Buffer 底层使用 ArrayBuffer
console.log(buf.buffer);       // ArrayBuffer
console.log(buf.byteOffset);   // 偏移量
console.log(buf.byteLength);   // 实际长度

// 注意:小 Buffer 使用池,buffer 可能很大
const small = Buffer.allocUnsafe(10);
console.log(small.buffer.byteLength); // 8192 (池大小)

核心区别

特性BufferTypedArray
环境Node.js 专有浏览器 + Node.js
编码支持 utf8/base64/hex 等仅二进制
方法toString/write/sliceset/subarray
创建Buffer.from/allocnew Uint8Array
性能I/O 优化通用数组操作

互操作

javascript
// TypedArray → Buffer
const uint8 = new Uint8Array([1, 2, 3]);
const buf = Buffer.from(uint8); // 拷贝数据

// Buffer → TypedArray
const buf2 = Buffer.from([1, 2, 3]);
const uint8_2 = new Uint8Array(buf2); // 拷贝数据
const uint8_3 = buf2; // 直接使用(Buffer 本身就是 Uint8Array)

4. 如何高效拼接多个 Buffer?

使用 Buffer.concat()

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

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

// 指定总长度(性能优化)
const result2 = Buffer.concat([buf1, buf2, buf3], 12);

性能陷阱

javascript
// ❌ 错误:O(n²) 复杂度
let result = Buffer.alloc(0);
for (let i = 0; i < 1000; i++) {
  result = Buffer.concat([result, Buffer.from('data')]);
}

// ✅ 正确:O(n) 复杂度
const chunks = [];
for (let i = 0; i < 1000; i++) {
  chunks.push(Buffer.from('data'));
}
const result = Buffer.concat(chunks);

流式数据拼接

javascript
class BufferCollector {
  constructor() {
    this.chunks = [];
    this.length = 0;
  }
  
  add(chunk) {
    this.chunks.push(chunk);
    this.length += chunk.length;
  }
  
  toBuffer() {
    return Buffer.concat(this.chunks, this.length);
  }
}

const collector = new BufferCollector();
stream.on('data', chunk => collector.add(chunk));
stream.on('end', () => {
  const result = collector.toBuffer();
});

预分配优化

javascript
// 已知总大小时,预分配更快
const chunks = [buf1, buf2, buf3];
const totalLength = chunks.reduce((sum, buf) => sum + buf.length, 0);

const result = Buffer.allocUnsafe(totalLength);
let offset = 0;
for (const chunk of chunks) {
  chunk.copy(result, offset);
  offset += chunk.length;
}

5. Buffer 的内存是如何管理的?

内存分配策略

  1. 小 Buffer(< 4KB):从 8KB 的预分配池中分配
  2. 大 Buffer(≥ 4KB):直接分配独立的 ArrayBuffer
javascript
// 小 Buffer 使用池
const small = Buffer.allocUnsafe(100);
console.log(small.buffer.byteLength); // 8192 (池大小)

// 大 Buffer 独立分配
const large = Buffer.allocUnsafe(4096);
console.log(large.buffer.byteLength); // 4096 (精确大小)

Buffer Pool 机制

javascript
// Node.js 内部维护一个 8KB 的池
let poolOffset = 0;
const poolSize = 8192;
let allocPool = createPool();

function createPool() {
  poolOffset = 0;
  return new ArrayBuffer(poolSize);
}

function allocate(size) {
  if (size >= poolSize >>> 1) {
    // 大 Buffer:独立分配
    return new ArrayBuffer(size);
  }
  
  if (poolOffset + size > poolSize) {
    // 池空间不足:创建新池
    allocPool = createPool();
  }
  
  // 从池中分配
  const offset = poolOffset;
  poolOffset += size;
  return { buffer: allocPool, offset };
}

垃圾回收

javascript
// Buffer 的内存由 V8 垃圾回收器管理
function createLargeBuffer() {
  const buf = Buffer.alloc(100 * 1024 * 1024); // 100MB
  return buf;
}

let buf = createLargeBuffer();
buf = null; // 标记为可回收

// 强制 GC(仅用于测试)
if (global.gc) {
  global.gc();
}

内存泄漏风险

javascript
// ⚠️ slice 共享内存,可能导致大 Buffer 无法释放
const large = Buffer.alloc(100 * 1024 * 1024);
const small = large.slice(0, 100); // 引用整个 100MB

// ✅ 使用 Buffer.from 创建副本
const small2 = Buffer.from(large.slice(0, 100));
// 现在 large 可以被回收

监控内存使用

javascript
const used = process.memoryUsage();
console.log({
  rss: `${Math.round(used.rss / 1024 / 1024)}MB`,        // 总内存
  heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`, // 堆总量
  heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,   // 堆使用
  external: `${Math.round(used.external / 1024 / 1024)}MB`,   // C++ 对象(Buffer)
});