六维教程

Node.js buffer 模块

Node.js events 模块 讲完事件,还有一个基础概念没讲。文件内容、网络传输在底层都是二进制字节,JS 原生字符串处理不了这些字节流。这一篇解决核心问题,Buffer 是什么,怎么用它处理二进制数据。buffer 模块是全局的,不需要 require 就能直接用。

Buffer 是什么

Buffer 是在 V8 堆内存之外分配的原始二进制数据,本质是 Uint8Array 的子类,所以数组的方法它基本都有。

// 不需要 require,全局可用
const buf = Buffer.from('hello');
console.log(buf);
// 输出 <Buffer 68 65 6c 6c 6f>

每个数字代表一个字节,68 是字母 h 的十六进制 ASCII 码,65 是 e,以此类推。之前 Node.js fs 模块 里不传 encoding 读出来的就是这种对象。

为什么需要 Buffer

JS 字符串用的是 UTF-16 编码,字符长度和字节数对不上。中文”你”在 UTF-8 下占 3 个字节,但字符串长度是 1,字符串天然不适合表示字节流。

而文件系统和网络协议都是按字节工作的。读图片、传文件、做加密,到处是字节流,所以必须有专门的数据类型,这就是 Buffer 存在的理由。

创建 Buffer

Buffer.from 从字符串创建

// 第二个参数是源字符串的编码,默认 utf8
const a = Buffer.from('hello');
const b = Buffer.from('你好');
console.log(a.length, b.length);
// 输出 5 6,中文一个字占 3 字节

Buffer.from 从数组创建

const c = Buffer.from([104, 101, 108, 108, 111]);
console.log(c.toString());
// 输出 hello

Buffer.alloc 分配固定大小

// 分配 10 字节,用 0 填充
const d = Buffer.alloc(10);
console.log(d);
// 输出 <Buffer 00 00 00 00 00 00 00 00 00 00>

// 指定填充值,65 是字母 A 的 ASCII 码
const e = Buffer.alloc(10, 65);
console.log(e.toString());
// 输出 AAAAAAAAAA

Buffer.alloc 会先清空内存再分配,安全。还有个不安全的 Buffer.allocUnsafe,速度快但不清理旧内存,可能读到残留数据,新手一律用 alloc 就好。

转换 toString

toString 可以指定输出编码。

const buf = Buffer.from('你好 Node.js');

console.log(buf.toString('utf8'));
// 你好 Node.js

console.log(buf.toString('hex'));
// e4bda0e5a5bd204e6f64652e6a73 十六进制

console.log(buf.toString('base64'));
// 5L2g5aW9IE5vZGUuanM=

三种编码的典型场景。utf8 是常规文本。hex 常用于展示和存储字节,比如日志里记录二进制内容。base64 用于把二进制塞进文本场景,比如图片转 base64 后放进 JSON 或 HTML 里传输。

Buffer 与 ArrayBuffer 与 Uint8Array

类型 内存位置 特点
Buffer V8 堆外 Node.js 专用,带编码转换等便捷方法
ArrayBuffer 堆外原始内存 只存字节,没有任何读写方法
Uint8Array 视情况 标准 JS 类型化数组,Buffer 的父类

三者的关系是,ArrayBuffer 是底层原始内存,Uint8Array 是它的视图,Buffer 是 Uint8Array 的子类,加上了 Node.js 生态的便捷方法。日常开发用 Buffer 就够了。

注意点

Buffer 内容可变

const buf = Buffer.from('hello');

// 直接按下标改字节,72 是 H 的 ASCII 码
buf[0] = 72;
console.log(buf.toString());
// 输出 Hello

Buffer 和字符串不一样,内容可以原地修改,下标直接对应字节。

slice 共享内存

const buf = Buffer.from('hello world');
const part = buf.slice(0, 5);

// 修改切片,原 Buffer 也跟着变
part[0] = 72;
console.log(buf.toString());
// 输出 Hello world

slice 返回的是共享同一块内存的视图,不是拷贝。改切片等于改原数据,想要独立副本要用 Buffer.from(part)

实践

读一张图片转成 base64 字符串,再把 base64 转回 Buffer 写回新文件,对比两个文件验证一致。

// imgBase64.js
const fs = require('fs').promises;

async function main() {
  // 1. 读图片,不传 encoding 得到 Buffer
  const img = await fs.readFile('photo.png');

  // 2. Buffer 转 base64 字符串
  const base64 = img.toString('base64');
  console.log('图片大小', img.length, '字节');
  console.log('base64 长度', base64.length, '字符');

  // 3. base64 转回 Buffer
  const restored = Buffer.from(base64, 'base64');

  // 4. 写回新文件
  await fs.writeFile('photo_copy.png', restored);

  // 5. 对比两个文件是否一致
  const copy = await fs.readFile('photo_copy.png');
  const same = img.equals(copy);
  console.log('两个文件一致', same);
}

main().catch(err => console.error('失败', err.message));

准备一张图片并运行,没有图片时先用命令生成一个测试文件。

# 生成 100 字节的测试文件
node -e "require('fs').writeFileSync('photo.png', Buffer.alloc(100, 65))"
node imgBase64.js

输出类似。

图片大小 100 字节
base64 长度 136 字符
两个文件一致 true

Buffer.equals 逐字节比较两个 Buffer 是否相同。base64 的长度会比原字节多出约三分之一,这是 base64 编码本身的开销,每 3 个字节被拆成 4 个字符表示。

到这里异步编程的前置基础都齐了,回调、事件、二进制都有了。下一步进入异步的核心难点,Node.js 回调与回调地狱

上一篇
Node.js events 模块
下一篇
Node.js 回调与回调地狱