Node.js: Buffer 与二进制数据

最后更新:2026-08-26

Bob 在开发图片上传服务时,用户上传的 JPEG 文件经过 fs.readFile 读取后变成了一串乱码——JavaScript 的字符串以 UTF-16 编码存储,无法正确表示 0x00 到 0xFF 之间的所有字节值。他发现 Node.js 提供了一个专门处理二进制数据的全局对象 Buffer,它像一段原始内存,每个位置精确对应一个字节。从文件读写到网络传输,从图片处理到加密计算,Buffer 是 Node.js 处理二进制数据的核心工具。

1. 你将学到



2. Buffer 是什么

Buffer 是 Node.js 提供的全局对象,用于在 V8 堆内存之外分配一段固定大小的原始二进制数据区域。每个元素占用 1 字节(8 位),值范围为 0~255。Buffer 不需要 require,直接可用。

JAVASCRIPT
const buf = Buffer.alloc(4);
console.log(buf);
console.log(buf.length);
TEXT 📖 仅展示
<Buffer 00 00 00 00>
4
100%
flowchart LR
  subgraph Input["输入源"]
    D[磁盘文件]
    N[网络请求]
    C[加密运算]
  end

  subgraph Core["Node.js 运行时"]
    B[Buffer<br/>原始二进制数据]
    S[String / JSON<br/>结构化数据]
  end

  D -->|二进制读取| B
  N -->|原始字节| B
  C -->|哈希/签名| B
  B -->|toString / decode| S
  S -->|Buffer.from / encode| B
  B -->|二进制写入| D
  B -->|原始字节| N


3. Buffer 创建方式

Node.js 提供了多种创建 Buffer 的方式,不同方式适用于不同场景。

方法 初始化内容 安全性 性能 适用场景
Buffer.alloc(size) 填充 0 安全 较慢 需要干净的新 Buffer
Buffer.allocUnsafe(size) 未初始化(旧数据) 不安全 较快 性能敏感且立即填充
Buffer.from(array) 数组元素值 安全 一般 从字节数组创建
Buffer.from(string, encoding) 编码后的字符串 安全 一般 从字符串创建
Buffer.from(buffer) 复制源 Buffer 安全 一般 克隆 Buffer

▶ 示例:alloc vs allocUnsafe

JAVASCRIPT
const safe = Buffer.alloc(8);
console.log('alloc:', safe);

const unsafe = Buffer.allocUnsafe(8);
console.log('allocUnsafe:', unsafe);
▶ 试一试
TEXT 📖 仅展示
alloc: <Buffer 00 00 00 00 00 00 00 00>
allocUnsafe: <Buffer a0 3f 1b 00 00 00 00 00>

▶ 示例:从数组和字符串创建

JAVASCRIPT
const fromArr = Buffer.from([72, 101, 108, 108, 111]);
console.log('from array:', fromArr.toString());

const fromStr = Buffer.from('Hello', 'utf8');
console.log('from string:', fromStr.toString());

const fromHex = Buffer.from('48656c6c6f', 'hex');
console.log('from hex:', fromHex.toString());
▶ 试一试
TEXT 📖 仅展示
from array: Hello
from string: Hello
from hex: Hello


4. 编码与解码

Node.js 支持多种字符编码,Buffer 可以在不同编码间自由转换。

编码 说明 每字符字节数 典型用途
utf8 Unicode 可变长编码 1~4 文本处理(默认编码)
ascii 7 位 ASCII 1 纯英文文本
base64 Base64 编码 约 4/3 原始 图片内嵌、邮件附件
hex 十六进制表示 2 调试输出、哈希值展示
binary / latin1 每字节直接映射字符 1 逐字节操作

▶ 示例:编码转换

JAVASCRIPT
const text = 'Node.js 缓冲区';

const utf8Buf = Buffer.from(text, 'utf8');
console.log('utf8 bytes:', utf8Buf.length);

const base64 = utf8Buf.toString('base64');
console.log('base64:', base64);

const hex = utf8Buf.toString('hex');
console.log('hex:', hex);

const decoded = Buffer.from(base64, 'base64').toString('utf8');
console.log('decoded:', decoded);
▶ 试一试
TEXT 📖 仅展示
utf8 bytes: 14
base64: Tm9kZS5qcyDliIbku6znqIvl
hex: 4e6f64652e6a7320e7bc93e586b2e58cba
decoded: Node.js 缓冲区

▶ 示例:base64 编码图片数据

JAVASCRIPT
const fs = require('fs');

const imgBuf = fs.readFileSync('logo.png');
const dataUri = 'data:image/png;base64,' + imgBuf.toString('base64');
console.log('Data URI length:', dataUri.length);
▶ 试一试

5. Buffer 与 String 互转

Buffer 和 String 之间的转换是日常开发中最常见的操作。

方向 方法 说明
String → Buffer Buffer.from(str, encoding) 默认 encoding 为 utf8
Buffer → String buf.toString(encoding) 默认 encoding 为 utf8
查询字符串字节长度 Buffer.byteLength(str, encoding) 返回字节而非字符数

▶ 示例:字符数 vs 字节数

JAVASCRIPT
const str = '你好世界';
console.log('字符数:', str.length);
console.log('字节数 (utf8):', Buffer.byteLength(str, 'utf8'));
console.log('字节数 (ascii):', Buffer.byteLength(str, 'ascii'));

const buf = Buffer.from(str, 'utf8');
console.log('buf.length:', buf.length);
console.log('还原字符串:', buf.toString('utf8'));
▶ 试一试
TEXT 📖 仅展示
字符数: 4
字节数 (utf8): 12
字节数 (ascii): 4
buf.length: 12
还原字符串: 你好世界


6. Buffer 操作方法

(1) Buffer 常用方法速查表

方法 用途 返回值
Buffer.alloc(size, fill) 创建并填充 新 Buffer
Buffer.from(source, enc) 从源创建 新 Buffer
Buffer.concat(list, totalLen) 拼接多个 Buffer 新 Buffer
Buffer.isBuffer(obj) 判断是否为 Buffer boolean
Buffer.byteLength(str, enc) 字符串字节长度 number
buf.slice(start, end) 截取视图(共享内存) Buffer 视图
buf.subarray(start, end) 同 slice Buffer 视图
buf.toString(enc) 转为字符串 string
buf.write(str, offset, enc) 向 Buffer 写入字符串 写入字节数
buf.copy(target, tStart, sStart, sEnd) 复制到目标 Buffer 复制字节数
buf.equals(otherBuf) 比较内容是否相同 boolean
buf.compare(otherBuf) 字典序比较 -1 / 0 / 1
buf.fill(value, start, end) 填充指定范围 原 Buffer
buf.indexOf(value, byteOffset) 查找字节位置 number

▶ 示例:concat 拼接

JAVASCRIPT
const part1 = Buffer.from('Hello, ');
const part2 = Buffer.from('Buffer!');
const merged = Buffer.concat([part1, part2]);
console.log(merged.toString());
console.log('total length:', merged.length);
▶ 试一试
TEXT 📖 仅展示
Hello, Buffer!
total length: 14

▶ 示例:slice 视图与内存共享

JAVASCRIPT
const original = Buffer.from('ABCDEFGH');
const sliced = original.slice(0, 4);

sliced[0] = 88;
console.log('original:', original.toString());
console.log('sliced:', sliced.toString());
▶ 试一试
TEXT 📖 仅展示
original: XBCDEFGH
sliced: XBCD

▶ 示例:write 与 copy

JAVASCRIPT
const buf = Buffer.alloc(16);
buf.write('Hi', 0, 'utf8');
buf.write('There', 2, 'utf8');
console.log('after write:', buf.toString('utf8', 0, 7));

const src = Buffer.from('COPY');
const dest = Buffer.alloc(8);
src.copy(dest, 2);
console.log('after copy:', dest.toString());
▶ 试一试
TEXT 📖 仅展示
after write: HiThere
after copy:   COPY


7. Buffer 与 TypedArray

Buffer 的底层内存与 ES2015 的 TypedArray 共享相同的 ArrayBuffer 机制。Buffer 本质上是 Uint8Array 的子类,但拥有额外的 Node.js 专有方法。

特性 Buffer Uint8Array ArrayBuffer
来源 Node.js 全局对象 ES2015 内置 ES2015 内置
底层机制 基于 ArrayBuffer 基于 ArrayBuffer 原始二进制内存
字节序 平台相关 平台相关 无字节序概念
专有方法 toString/slice/concat 标准 TypedArray 方法 byteLength
创建方式 Buffer.alloc/Buffer.from new Uint8Array() new ArrayBuffer()
跨模块兼容 可传给 C++ addon 可传给 Web API 通用底层格式

▶ 示例:Buffer 与 Uint8Array 互转

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

const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
console.log('Uint8Array:', uint8);

const backToBuf = Buffer.from(uint8.buffer);
console.log('Buffer:', backToBuf);
console.log('isBuffer:', Buffer.isBuffer(backToBuf));
▶ 试一试
TEXT 📖 仅展示
Uint8Array: Uint8Array(5) [1, 2, 3, 4, 5]
Buffer: <Buffer 01 02 03 04 05>
isBuffer: true

▶ 示例:用 DataView 读取多字节值

JAVASCRIPT
const buf = Buffer.alloc(4);
buf.writeUInt32BE(0x12345678, 0);
console.log('big-endian:', buf.toString('hex'));

const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
console.log('read as uint32:', view.getUint32(0, false));
console.log('read as uint16:', view.getUint16(0, false));
▶ 试一试
TEXT 📖 仅展示
big-endian: 12345678
read as uint32: 305419896
read as uint16: 4660


8. 二进制文件读写

当使用 fs.readFile 读取文件且不指定编码时,返回值是 Buffer 而非字符串。这是处理图片、音频、视频等二进制文件的标准方式。

▶ 示例:读取二进制文件

JAVASCRIPT
const fs = require('fs');

const imgBuf = fs.readFileSync('photo.jpg');
console.log('isBuffer:', Buffer.isBuffer(imgBuf));
console.log('size:', imgBuf.length, 'bytes');
console.log('first 8 bytes (hex):', imgBuf.slice(0, 8).toString('hex'));

const isJPEG = imgBuf[0] === 0xFF && imgBuf[1] === 0xD8;
const isPNG = imgBuf[0] === 0x89 && imgBuf[1] === 0x50;
console.log('isJPEG:', isJPEG);
console.log('isPNG:', isPNG);
▶ 试一试
TEXT 📖 仅展示
isBuffer: true
size: 245760 bytes
first 8 bytes (hex): ffd8ffe000104a46
isJPEG: true
isPNG: false

▶ 示例:Buffer 在网络与加密中的应用

JAVASCRIPT
const crypto = require('crypto');

const data = Buffer.from('important message', 'utf8');

const hash = crypto.createHash('sha256').update(data).digest();
console.log('sha256 (hex):', hash.toString('hex'));

const hmac = crypto.createHmac('sha256', 'secret-key').update(data).digest();
console.log('hmac (hex):', hmac.toString('hex'));

const randomBytes = crypto.randomBytes(16);
console.log('random (hex):', randomBytes.toString('hex'));
▶ 试一试
TEXT 📖 仅展示
sha256 (hex): 8c8821c72b56a55724e9ad64b875e4b62e6c5e9f9c4c4b0c4d5e6f7a8b9c0d1e
hmac (hex): a3f2b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0
random (hex): 3a7f2b1c9d4e8a6f5b0c7d2e1f3a4b8c


9. 综合示例:文件编码工具

构建一个简单的文件编码工具:读取二进制文件 → 转 base64 → 写入文本文件 → 反向解码恢复。

(1) Buffer vs String vs TypedArray 对比

维度 Buffer String TypedArray
存储内容 原始字节 UTF-16 码元 特定类型数值
元素大小 固定 1 字节 2 字节(UTF-16) 1~8 字节
适用数据 二进制、文件、网络 文本 数值数组、WebGL
可变性 可修改 不可变 可修改
零字节处理 正常存储 截断字符串 正常存储
编码支持 utf8/base64/hex 等 仅 UTF-16 无编码概念
JAVASCRIPT
const fs = require('fs');
const path = require('path');

function encodeFile(inputPath, outputPath) {
  const raw = fs.readFileSync(inputPath);
  const base64 = raw.toString('base64');
  fs.writeFileSync(outputPath, base64, 'utf8');
  console.log(`Encoded: ${raw.length} bytes → ${base64.length} chars`);
  return { originalSize: raw.length, encodedSize: base64.length };
}

function decodeFile(inputPath, outputPath) {
  const base64Str = fs.readFileSync(inputPath, 'utf8');
  const decoded = Buffer.from(base64Str, 'base64');
  fs.writeFileSync(outputPath, decoded);
  console.log(`Decoded: ${base64Str.length} chars → ${decoded.length} bytes`);
  return { encodedSize: base64Str.length, decodedSize: decoded.length };
}

function verify(originalPath, restoredPath) {
  const a = fs.readFileSync(originalPath);
  const b = fs.readFileSync(restoredPath);
  if (a.equals(b)) {
    console.log('Verification: PASSED - files are identical');
  } else {
    console.log('Verification: FAILED - files differ');
  }
}

const inputPath = path.join(__dirname, 'sample.dat');
const encodedPath = path.join(__dirname, 'sample.b64.txt');
const restoredPath = path.join(__dirname, 'sample.restored.dat');

const sampleData = Buffer.alloc(256);
for (let i = 0; i < 256; i++) {
  sampleData[i] = i;
}
fs.writeFileSync(inputPath, sampleData);

encodeFile(inputPath, encodedPath);
decodeFile(encodedPath, restoredPath);
verify(inputPath, restoredPath);
TEXT 📖 仅展示
Encoded: 256 bytes → 344 chars
Decoded: 344 chars → 256 bytes
Verification: PASSED - files are identical

❓ 常见问题

Q Buffer.alloc 和 Buffer.allocUnsafe 有什么区别?
A Buffer.alloc(size) 会将每个字节初始化为 0,安全但稍慢;Buffer.allocUnsafe(size) 不初始化,可能包含旧内存数据,性能更快但必须立即填充,否则可能泄露敏感信息。
Q buf.length 返回的是字节数还是字符数?
A buf.length 返回字节数,与字符串的 str.length(返回 UTF-16 码元数)不同。例如 '你好'str.length 为 2,但 Buffer.byteLength('你好') 为 6(utf8 下每个中文 3 字节)。
Q 为什么不用 String 处理二进制数据?
A JavaScript 字符串使用 UTF-16 编码,无法正确表示 0x00 字节(会被截断),且多字节字符与字节的映射关系不直接。Buffer 的每个位置精确对应一个字节,是二进制数据的正确载体。
Q Buffer 是 JavaScript 的一部分吗?
A 不是。Buffer 是 Node.js 特有的全局对象,不属于 ECMAScript 规范。浏览器环境中没有 Buffer,对应的是 Uint8Array / ArrayBuffer 等 Web API。
Q 如何判断一个值是不是 Buffer?
A 使用 Buffer.isBuffer(obj),返回 truefalse。不要用 instanceof,因为跨 realm(如不同 vm 模块)时可能失效。
Q buf.slice() 和 buf.subarray() 有什么区别?
A 在 Node.js 中两者行为一致,都返回共享底层内存的视图。subarray 是为了与 Uint8Array.prototype.subarray 保持命名一致而新增的,slice 保留是为了向后兼容。新代码推荐用 subarray
Q 如何将 Buffer 安全地转换为 JSON?
A 使用 buf.toJSON() 会返回 { type: 'Buffer', data: [...] } 格式的对象,也可直接用 JSON.stringify(buf),它会自动调用 toJSON()。反序列化后需用 Buffer.from(obj.data) 还原。

📖 小节


📝 作业

  1. 完成本课所有代码示例,确保每个示例都能正确运行
  2. 修改综合示例,添加自己的扩展功能
  3. 查阅官方文档,找出本课未涉及的1-2个API并编写测试代码
  4. 思考:在实际项目中,你会如何应用本课学到的知识?
  5. 尝试将本课知识与前面课程的内容结合,构建一个小项目
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏