目录

node buffer 模块常用方法、安全与性能最佳实践

基础术语

  • 编码(Encoding):将字符串转换为 Buffer(二进制数据)。
  • 解码(Decoding):将 Buffer 转换为字符串。
  • 字节序是指计算机在处理多字节数据时,根据预设规则或目标平台的字节序要求,字节的存储或传输的顺序
    • 小端序(Little-Endian):低位字节在后
    • 大端序(Big-Endian):高位字节在前

作用

Buffer 类用于表示 固定长度的字节序列,用于处理二进制数据

该类是 JavaScript 内置的 <Uint8Array> 类的子类,并扩展了适用于更多用例的方法。在 Node.js API 中,任何支持 Buffer 的地方也接受普通的 <Uint8Array> 对象。

使用前提

无前提可直接使用,但是从 buff 模块中引用的话可读性好

ini 复制代码
const Buffer = require('buffer');

buffer 与字符串的转换

Buffer 和字符串之间的转换可通过指定字符编码实现。默认使用 UTF-8 编码,但支持多种其他编码格式。

  • unicode 编码
    • utf8: 多字节编码,支持所有 Unicode 字符,是默认编码。若解码时发现无效字节,会用 U+FFFD (�) 替代错误字符
    • utf16le:每个字符用 2 或 4 字节表示,仅支持小端序
  • 单字节编码
    • latin1: 仅支持 U+0000U+00FF 的字符,超出部分会被截断
  • 二进制到文本编码
    • base64:支持 URL 和文件名安全的 Base64 变种(RFC 4648),忽略空格和换行符
    • base64url:类似 base64,但编码时省略填充符 =
    • hex:每字节编码为两个十六进制字符,解码时若长度非偶数会截断数据
  • 传统编码(不推荐使用)
    • ascii :仅支持 7 位 ASCII,解码时会清除高位比特(等效于 latin1)。
    • ucs-2:已弃用,完全由 utf16le 替代。

可迭代性

可以使用 for of 来进行迭代,也可以使用 values 、 keys 、 entries 创建迭代器

Buffer.from

作用

根据传入值生成二进制数据存储入一个新的 Buffer 中。

语法

整型数组、TypedArray、Buffer 初始化

csharp 复制代码
Buffer.from(dataArr)
  • 接收一个数组,该数组每个元素的字节长度必须为 1 的整数【也就是 0-255】。如果超过长度则会被截断。该数组可以是普通数组或类数组对象
  • 输入是 TypedArray(如 Uint16Array)或 Buffer ,则会直接复制数据而非共享内存。

ArrayBuffer 初始化

csharp 复制代码
Buffer.from(dataArr, [byteOffset, length])
  • dataArr 为 ArrayBufferTypedArray.buffer 【传递底层的 ArrayBuffer 数据】时,会与 dataArr 共享内存
  • byteOffset 表示从下标为多少的字节开始共享空间。默认值为 0
  • length 表示总共共享多少个字节的空间,默认值为 arrayBuffer.byteLength - byteOffset.

对象初始化

csharp 复制代码
 Buffer.from(object[, offsetOrEncoding, length])
  • object :支持 Symbol.toPrimitive自定义对象到原始值(如数字、字符串)的隐式类型转换行为, 参数 hint 表示要转换为什么类型的原始数据:number、default、string 】或 valueOf() 方法的对象,将其转换为字符串后生成 Buffer。【若对象不支持上述方法或无法转换,抛出 TypeError。】
  • offsetOrEncoding:为数字表示偏移量,为字符串表示将字符串按指定编码转换为 Buffer 实例
  • length: 表示转换多少字符

字符串初始化

csharp 复制代码
 Buffer.from(string[, encoding])
  • object :支持 Symbol.toPrimitive自定义对象到原始值(如数字、字符串)的隐式类型转换行为, 参数 hint 表示要转换为什么类型的原始数据:number、default、string 】或 valueOf() 方法的对象,将其转换为字符串后生成 Buffer。【若对象不支持上述方法或无法转换,抛出 TypeError。】
  • encoding:表示将字符串按指定编码转换为 Buffer 实例,默认utf8

Buffer实例.copyBytesFrom() 比较

方法 Buffer.from(typedArray) Buffer实例.copyBytesFrom(typedArray)
操作对象 创建新 Buffer 操作已存在的 Buffer
内存分配 每次调用都分配新内存 复用现有内存
适用场景 初始化一次性数据 高性能数据更新、流式处理
字节序处理 直接复制底层字节 自动转换字节序

二者如何选择:

  • Buffer.from() :需要快速创建一个新 Buffer,且后续不需要修改或复用内存。
  • copyBytesFrom():需要高性能地更新 Buffer 内容(如实时数据处理),或处理非 Uint8Array 类型时不需复用内存【Buffer.alloc()分配空间 实例.copyBytesFrom 数据初始化】。

Buffer.alloc

作用

创建新 Buffer 实例并安全初始化。

安全但是比 Buffer.allocUnsafe 慢

语法

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

  • size 指定新 buffer 的字节长度
  • fill 每个字节填充的值,值的类型可以为:字符串/Buffer/Uint8Array/整数
  • encoding 用于解析 fill 的编码格式

返回 Buffer 新实例

Buffer.allocUnsafe

作用

创建新 Buffer 实例

用于高性能内存分配的静态方法,不会进行内存初始化,size ≤ Buffer.poolSize / 2 时从内存池中分配内存。

适用场景

  • 短期持有小内存的 Buffer 场景
    • 因为长期持有会导致内存池空间碎片化,影响后续分配内存效率
  • 高性能场景

语法

Buffer.allocUnsafe(size)

  • size 指定新 buffer 的字节长度

返回 Buffer 新实例。

Buffer.allocUnsafeSlow

作用

创建新 Buffer 实例

不会进行内存初始化,也不会从内存池中分配内存。独立内存块无共享风险

适用场景

  • 长期持有小内存的 Buffer 场景,防止内存池碎片化

语法

Buffer.allocUnsafeSlow(size)

  • size 指定新 buffer 的字节长度

返回 Buffer 新实例。

Buffer.concat()

作用

用于使用多个 Buffer 实例数据来构造一个新的 Buffer 实例

Buffer.allocUnsafe() 类似,Buffer.concat() 可能复用预分配的内存池,减少系统调用次数,若合并后的总长度超过 Buffer.poolSize(默认 8KB),可能直接分配独立内存块以提高性能

语法

css 复制代码
Buffer.concat(list,[ totalLength])
  • list:为一个 Buffer 或 Uint8Array 实例的数组,表示这些数组中的每一项都会成为合并素材
  • totalLength: 表示再进行多少个字节后停止合并,若是实际数据字节长度小于 totalLength ,则剩余内存使用 0 填充。默认值为 list 所有元素字节长度之和。

cookbook

在需要精确控制内存或高频操作时,优先指定 totalLength 参数以避免自动计算的开销。

Buffer.compare()

作用

用于判断两个 Buffer的大小关系。

语法

scss 复制代码
Buffer.compare(buf1, buf2)

返回值

  • 0:两个 Buffer 内容完全相同。
  • 1buf1 在二进制字典序中大于 buf2,排序时应排在后面。
  • -1buf1 在二进制字典序中小于 buf2,排序时应排在前面。

比较机制

  • 逐个字节比较每个索引位置的十六进制值,直到找到差异。
  • Buffer 的编码(如 UTF-8、Hex)需一致,否则比较结果可能不符合预期
  • 若 Buffer 长度不同,较短 Buffer 的缺失部分视为 0x00

Buffer.copyBytesFrom()

作用

用于从 TypedArray 直接复制底层内存到新 Buffer 的高效方法,适用于需要快速处理二进制数据的场景。

语法

sql 复制代码
Buffer.copyBytesFrom(view[, offset, length])

参数

  • view:必填参数,类型为 TypedArray(如 Uint8ArrayUint16Array)。表示要复制的底层内存数据源。
  • offset(默认 0)起始元素索引(非字节偏移),例如 Uint16Array 每个元素占 2 字节,offset=1 表示从第二个元素开始复制。
  • length(默认 view.length - offset):要复制的元素数量。实际复制的字节数为 length * view.BYTES_PER_ELEMENT

返回值

返回一个包含复制数据的新 Buffer,其长度由复制的元素数量及类型决定。

机制

  • 直接内存复制:直接操作 TypedArray 的底层内存,避免逐元素转换的性能损耗。例如,Uint16Array 的每个元素占 2 字节,复制时会按内存布局直接拷贝字节。
  • 数据独立性:复制后的 Buffer 与原 TypedArray 数据完全独立,后续修改原数据不影响 Buffer 内容。

Buffer实例.fill

作用

用指定值填充 Buffer 实例的指定字节范围

语法

语法

css 复制代码
buffer.fill(value, [offset], [end], [encoding])
  • value | | | 默认值为 0,用于填充 buffer 每个字节的值,多余内容会被截断,截断时有数据泄露风险。
  • offset 从 buffer 的下标为几的字节开始填充。
  • end 表示到下标为 end 的字节停止填充【end 下标本身不会被填充】
  • encoding 表示解析数据时的编码

示例

arduino 复制代码
// 以utf8的格式解析 c ,并且将内容从下标为1的字节内存开始填充,填充完19后停止填充
buffer.fill('c', 1, 20, 'utf8')
// 以utf8的格式解析 c ,并且将内容从下标为1的字节内存开始填充,填充完 buffer 后停止填充
buffer.fill('c', 1, 'utf8')
// 以utf8的格式解析 c ,并且将 buffer 每个字节都填充此值
buffer.fill('c','utf8')

Buffer实例.toString

作用

将 buffer 的内容转换为指定编码的字符串

特性

  1. 多字节字符处理

解码多字节字符(如中文)时需确保 startend 位置对齐字符边界。例如 UTF-8 中一个汉字占 3 字节,若截断位置非 3 的倍数,可能导致部分字符丢失

示例:

arduino 复制代码
javascript


const buf = Buffer.from('今天有沙尘暴');
console.log(buf.toString('utf8', 3, 9)); // 输出:天有
  1. 编码兼容性
    • hex:将每个字节转为两个十六进制字符(如 74c3a9)。
    • base64:生成 Base64 编码字符串(如 dGVzdA==)。
    • utf8:支持 Unicode 字符,自动处理多字节序列。

语法

语法

vbscript 复制代码
buf.toString([encoding, [start], [end]])
  • start(默认 0)从 buf 的下标为 start 字节开始解码。若超出 Buffer 长度,返回空字符串。
  • end (默认 buf.length)解码的结束字节位置(不包含该位置)。若 end > buf.length,自动修正为 Buffer 末尾。

返回值:解码后的字符串。若遇到无效 UTF-8 字节,替换为 U+FFFD(�)

示例

arduino 复制代码
// 以utf8的格式解析 c ,并且将内容从下标为1的字节内存开始填充,填充完19后停止填充
buffer.fill('c', 1, 20, 'utf8')
// 以utf8的格式解析 c ,并且将内容从下标为1的字节内存开始填充,填充完 buffer 后停止填充
buffer.fill('c', 1, 'utf8')
// 以utf8的格式解析 c ,并且将 buffer 每个字节都填充此值
buffer.fill('c','utf8')

Buffer实例.write

作用

用指定值填充 Buffer 实例的指定字节范围,若超出 Buffer 长度,写入操作将被忽略。

语法

语法

css 复制代码
buf.write(string, [offset], [length], [encoding]

返回成功写入的字节个数

编码与字节计算

不同编码的字符串占用字节数不同。例如:

    • UTF-8 编码的 '½'(Unicode U+00BD)占 2 字节(0xC2 0xBD)****。
    • ASCII 编码的 'a' 仅占 1 字节****。
多字节字符处理

写入多字节字符时需确保 Buffer 有足够空间。若空间不足,仅写入完整字符,,避免产生乱码。如:

arduino 复制代码
const buf = Buffer.alloc(5);
buf.write('€', 0, 3, 'utf8');  // '€' UTF-8 编码为 3 字节(0xE2 0x82 0xAC)
console.log(buf.toString('utf8'));  // 输出: €(完整写入)
arduino 复制代码
const buffer = Buffer.alloc(10);
const length = buffer.write('abcd', 8);
console.log(`${length} bytes: ${buffer.toString('utf8', 8, 10)}`);
// 输出: 2 bytes: ab

Buffer实例.toJSON

作用

用于将 Buffer 对象转换为 JSON 格式

语法

css 复制代码
buf.toJSON(): { type: 'Buffer', data: Array<number> }

一个包含 typedata 属性的 JSON 对象

  • type:固定为 'Buffer',标识数据类型。
  • data:数组形式存储的 Buffer 二进制字节(十进制数值)

如何反序列化

通过 JSON.parse() 函数,可自动将 JSON 还原为 Buffer 。

javascript 复制代码
// 序列化(无编码转换)
const buf = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // ASCII: Hello
const json = JSON.stringify(buf); // {"type":"Buffer","data":[72,101,108,108,111]}

// 反序列化(直接重建二进制)
const restoredBuf = Buffer.from(JSON.parse(json));
console.log(restoredBuf); // <Buffer 48 65 6c 6c 6f>

当然若是字符串格式与Buffer 转换后的格式一致,也会转换为 Buffer 实例

ini 复制代码
const restoredBuf = Buffer.from(JSON.parse('{"type":"Buffer","data":[72,101,108,108,111]}'));
console.log(restoredBuf); // <Buffer 48 65 6c 6c 6f>

Buffer 实例.length

返回Buffer 的字节长度

Buffer 实例.copy

作用

用于将指定数据覆盖 Buffer 指定区域的数据,支持内存区域重叠的场景

应用场景

  1. 协议数据重组
    在网络通信中,将分片接收的 Buffer 数据合并到目标 Buffer 的指定位置
  2. 内存高效复用
    通过重叠复制实现内存复用,减少内存分配次数。例如,滑动窗口协议中的缓冲区管理
  3. 数据截取与拼接
    提取源 Buffer 的特定片段(如文件头、校验码)并拼接到目标 Buffer 中

语法

css 复制代码
buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

参数

  • target,要复制的数据源
  • targetStart:默认值为 0 ,目标 Buffer 的写入起始偏移量(单位:字节)
  • sourceStart: 要覆盖的 Buffer 实例字节区域的起始字节
  • sourceEnd: 要覆盖的 Buffer 实例字节区域的结束字节【不包含】。若 sourceEnd > buf.length,则自动修正为 buf.length

targetStartsourceStart 为负数,或 targetStart + 复制长度 > target.length,会抛出 ERR_INDEX_OUT_OF_RANGE 错误。

返回值

实际复制的字节数,值为 sourceEnd - sourceStart

cookbook

  1. 性能优化
    • 批量复制时,优先使用 Buffer.concat() 代替多次 copy,以减少内存操作次数6
    • 避免在循环中频繁创建新 Buffer,可复用现有内存空间。
  1. 错误处理
    • 使用 try-catch 捕获可能的 ERR_INDEX_OUT_OF_RANGE 错误,确保参数合法性45

安全问题

fill 后由于引擎优化引起的安全问题

虽然 fill 操作的是物理内存,即将内存数据覆盖,但是在 js 引擎优化或是垃圾回收的时候,可能会导致 fill 操作被延迟或合并,覆盖前的数据被缓存到内存中

可以通过二次数据覆盖尽可能减少这种情况的出现。

php 复制代码
const { randomFillSync } = require('crypto');
const sensitiveData = Buffer.from("pass");
// 使用加密安全的随机数据覆盖,破坏内存数据的可预测性,增加攻击者恢复原始数据的难度。
randomFillSync(sensitiveData);
sensitiveData.fill(0); // 二次覆盖

共享内存引发的安全问题

场景

共享 ArrayBuffer 内存时,可能访问到超出预期范围的数据,因为 ArrayBuffer 的实际内存可能比当前视图(如 TypedArray)覆盖的范围更广。

比如 Buffer.from 参数为一个 ArrayBuffer 时,新的 Buffer 实例会与之共享内存,但若是没有手动限制共享的内存长度或是共享内存长度过长,就会新 Buffer 实例可以访问到比当前视图更长的内存空间。

ini 复制代码
// 创建包含敏感数据的 ArrayBuffer
const ab = new ArrayBuffer(8);
const view = new Uint8Array(ab);
view.set([0x73, 0x65, 0x63, 0x72]); // "secr"

// 创建共享内存的 Buffer,但未限制范围
const buf = Buffer.from(ab);
console.log(buf.toString()); 
// 输出 "secr\u0000\u0000\u0000\u0000"(残留内存的零值可能被后续覆盖为其他数据)

解决方案

  1. 限制内存共享的空间长度,比如 Buffer.from 的第三个参数。
  2. 内存隔离 :对高敏感场景建议完全拷贝而非共享(Buffer.from(view.buffer.slice(...))),避免其他视图修改导致数据污染。

使用未初始化的 Buffer 分配方法

场景

Buffer.allocUnsafe()Buffer.allocUnsafeSlow()创建 Buffer 时,这些方法直接从内存池或操作系统的空闲内存块分配内存,跳过了初始化步骤,底层内存可能残留其他进程或应用的历史数据。

arduino 复制代码
// 创建未初始化的 Buffer
const buf = Buffer.allocUnsafe(4);
console.log(buf.toString('hex')); // 可能输出旧数据,如 "a1b2c3d4"

// 假设此前内存中存有密码 "pass"
const sensitiveData = Buffer.from("pass");
sensitiveData.fill(0); // 未正确清空

// 新 Buffer 复用同一内存池
const newBuf = Buffer.allocUnsafe(4);
console.log(newBuf.toString()); // 可能残留 "pass"

解决方案

  1. 每次使用完 Buffer 后及时清空内存
  2. 在进行未初始化的 Buffer 分配方法的调用后,对新内存进行初始化操作。

编码转换或截断时的数据泄露

问题原因

使用 Buffer.from(string, encoding)Buffer.transcode() 转换编码时,若目标编码不支持某些字符,可能因替换字符(如 �)或截断不完整导致敏感数据暴露,因为 编码转换未正确处理的这些字节,残留数据是未被覆盖的。

解决方案

双重 encoding 转换,保证就算数据截断或是解析错误,也会将解析错误的原始数据覆盖掉。

php 复制代码
function safeEncode(str, encoding = 'utf8') {
  const buf = Buffer.from(str, encoding);
  return Buffer.from(buf.toString(encoding), encoding); // 双重转换确保覆盖残留
}

内存池分配导致的安全问题

内存池

Node.js 默认预分配一个 8KB 的内存池(可通过 Buffer.poolSize 调整),用于高效管理小对象的频繁内存请求。内存池分为三种状态:

  • empty:未分配。
  • partial:部分分配。
  • full:完全分配

即使因内存分配释放产生空间碎片,内存池自身会通过 alignPool() 自动对齐管理

何时会使用内存池分配内存

当通过 Buffer.from() 创建 小对象(小于等于 Buffer.poolSize / 2Buffer 时)【内存共享并不属于创建小对象情况】或是 Buffer.allocUnsafe()Buffer.allocUnsafeSlow() 创建内存 时,Node.js 会优先从内存池中分配内存块,避免频繁向操作系统申请内存的开销。

而此时分配的内存并没有进行初始化可能会有原始数据残留。

若是小对象累计内存超过已申请的内存池大小时, 底层会调用createPool() 新建 8KB 内存池

安全隐患场景及解决方案

造成原因

当一个内存池分配的内存被释放掉后,下次请求新的内存时,会进行旧内存的复用。

若未及时清空内存池,新分配的 Buffer 可能读取到旧数据

风险类型

可以直接禁用内存池 Buffer.poolSize = 0; // 禁用内存池(牺牲性能换取安全)

风险类型 示例场景 解决方案
敏感数据泄露 内存池残留旧密码、密钥等 优先使用 Buffer.alloc()
内存内容不可预测 调试时输出乱码或异常数据 创建后立即初始化内存
攻击者利用未初始化内存 通过未初始化内存获取系统信息 严格校验输入参数类型和范围

防范手段总结

  1. 优先使用安全分配方法
    • 使用 Buffer.alloc() 初始化内存。
  1. 显式初始化内存或是在用完后覆盖清理内存
php 复制代码
const { randomFillSync } = require('crypto');
const sensitiveData = Buffer.from("pass");
// 使用加密安全的随机数据覆盖
randomFillSync(sensitiveData);
sensitiveData.fill(0); // 二次覆盖
  1. 限制共享内存范围
    • 通过 Buffer.from(arrayBuffer, offset, length) 明确内存边界。
  1. 解析文本时指定正确的字符编码,并且进行双重 encoding 转换,保证就算数据截断或是解析错误,也会将解析错误的原始数据覆盖掉。

高效场景最佳实践

js 复制代码
    1.  在创建 Buffer 时:
        a.  判断是copy数据还是自主创建数据
            i.  若是copy数据则使用 `Buffer.copyBytesFrom`,直接从 `TypedArray` 底层内存复制,避免逐字节操作的开销
        b.  判断是大内存(≥4KB)还是小内存
            i.  大内存则使用 Buffer.allocUnsafeSlow ,因为比 Buffer.allocUnsafe 快不用判断是否要在内存池中申请内存
            ii.  若是小内存则判断是否长期
                1.  长期 (如缓存池、持久化数据结构) 则使用 Buffer.allocUnsafeSlow ,防止内存池碎片化,导致后续分配效率低下
                2.  短期 (如临时网络包处理) 则使用 Buffer.allocUnsafe 利用内存池切割复用,减少系统调用
            iii.  若是无从得知,则使用 Buffer.allocUnsafe
    2.  在进行内存初始化时:使用 randomFillSync + fill 在双重覆盖保障安全性的同时保障覆盖高效
    3.  在进行数据拷贝的过程:
        a.  批量复制时,优先使用 `Buffer.concat()` 代替多次 `copy`,以减少内存操作次数
        b.  内存区域重叠的场景、将一个Buffer 中的数据片段覆盖另一个 Buffer 中某块数据内容,使用 Buffer实例.copy
        c.  以单个数据源创建新 Buffer 时,Buffer.copyBytesFrom
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
掘金一周12 分钟前
对话即服务:Spring Boot整合MCP让你的CRUD系统秒变AI助手 | 掘金一周 3.28
前端·人工智能·ai编程
Ch1oy21 分钟前
解锁 JavaScript DOM:节点操作的核心方法与最佳实践
开发语言·前端·javascript·ecmascript·html5
Jiude23 分钟前
🚨两月没动的项目突然爆红❗一场由 ESLint 和格式化配置缺失引发的血案🧩
前端·visual studio code·cursor
howcode23 分钟前
这次终于轮到前端给后端兜底了🤣
前端·javascript·vue.js
还是鼠鼠26 分钟前
Node.js 监听 GET 和 POST 请求并处理参数
前端·javascript·vscode·node.js·json
bnnnnnnnn36 分钟前
宝塔面板快速部署 Node.js + Express 后端,一步到位!
前端·后端·node.js
BUG解释工程师39 分钟前
如何判断浏览器同源标签页数量
前端
该怎么办呢43 分钟前
Cesium双击放大地图
javascript·cesium·webgis
程序员林北北1 小时前
深入解析 Vue3 响应式系统:原理、性能优化与应用场景
前端·javascript·vue.js·typescript·前端框架·html·es6
观默1 小时前
一起来搞低代码--华为TinyEngine搭建&源码开发
前端