Node.js Buffer.alloc 重复填充:ab 为什么变成 ababa,中文末尾又为何被切开

前天 3阅读

生成固定长度测试报文时,给 Buffer.alloc 传入五和字符串 ab,结果会出现 ababa。容量明明是五,为什么模式没有完整重复三次?因为第一个参数规定字节数,第二个参数提供填充模式;写满指定空间就停止,最后一轮可以只写模式的一部分。

先把容量和模式分开看

可以把缓冲区想成五个连续格子,ab 编码后占两个格子。先放两轮得到 abab,再放下一个 a,容量就用完了。模式长度不会反过来扩大缓冲区,填充值也不是只复制一次后留下零。省略填充值时才使用默认的零填充。

换成中文,格子仍按字节计算。本文使用的“中”在 UTF-8 中占三个字节,十六进制是 e4 b8 ad。四字节空间放进完整一轮后,还剩一个位置,因此最后留下第二轮的 e4。这个结果长度完全正确,却不再是一段完整的 UTF-8 文本。

Node.js Buffer.alloc 重复填充:ab 为什么变成 ababa,中文末尾又为何被切开

AI 生成概念配图:有限字节格子里重复放入同一模式,末尾只保留放得下的部分,不是实际运行截图。

同时检查长度、十六进制和文本

把下方代码保存为 fill-demo.cjs,用 node fill-demo.cjs 执行。示例在 Node.js v24.19.0 验证,只创建内存对象,不读写文件。每一项都带断言;屏幕显示用于解释现象,断言用于确认字节结果,任何不一致都会使进程失败。

const { Buffer } = require('node:buffer');
const assert = require('node:assert/strict');

const ascii = Buffer.alloc(5, 'ab', 'utf8');
assert.equal(ascii.toString('utf8'), 'ababa');
console.log('ascii:', ascii.length, ascii.toString('hex'), ascii.toString());

const partial = Buffer.alloc(4, '中', 'utf8');
assert.equal(partial.toString('hex'), 'e4b8ade4');
assert.equal(partial.toString('utf8'), '中\uFFFD');
console.log('partial:', partial.toString('hex'), JSON.stringify(partial.toString()));

const complete = Buffer.alloc(6, '中', 'utf8');
assert.equal(complete.toString('utf8'), '中中');
console.log('complete:', complete.toString('hex'), complete.toString());

const numeric = Buffer.alloc(3, 0x1234);
const pattern = Buffer.alloc(3, Buffer.from([0x12, 0x34]));
assert.equal(numeric.toString('hex'), '343434');
assert.equal(pattern.toString('hex'), '123412');
console.log('numeric:', numeric.toString('hex'));
console.log('pattern:', pattern.toString('hex'));

const text = Buffer.from('中'.repeat(2), 'utf8');
assert.equal(text.length, 6);
assert.equal(text.toString('utf8'), '中中');
console.log('text bytes:', text.length);

第一行应给出长度五、6162616261 和 ababa。partial 一行的原始内容是 e4b8ade4,解码显示“中�”;complete 一行则是 e4b8ade4b8ad 与“中中”。替换符出现在解码阶段,缓冲区本身保存的仍是那四个字节,解码不会自动补齐原本没有写入的数据。

数字填充值只提供一个字节

数值 0x1234 的低八位是 0x34,因此分配三个字节后得到 343434。它不会被拆成十二和三十四两个十六进制字节交替写入,也不表示把一个十六位整数按某种字节序存进去。需要明确的多字节模式时,应像 pattern 一项那样传入由两个字节组成的 Buffer。

pattern 的结果是 123412:两字节模式完整写一次,再写第一字节。这与开头的 ababa 遵循同一条规则。把数值和字节数组并排测试,可以避免把“填满空间”误当成“序列化数值”;真正写入协议整数时,还要另外选择宽度与字节序对应的写入方法。

文本需求应先完成文本再编码

如果业务目标是得到两个完整的“中”,最后一项先重复字符串,再用 Buffer.from 编码,得到六字节。不要把字符数量直接当成 Buffer.alloc 的容量,也不要仅靠最终显示没有乱码判断正确;有些截断恰好落在合法边界上,但重复次数已经改变。

第三个参数描述字符串填充值采用什么编码,不会把容量单位改成字符。调用前先写清需求:是固定字节数的测试模式,还是固定次数的文本重复。前者允许末尾截短模式,后者应保留完整文本;二者需要的验收条件不同,不能共用一个模糊的“长度正确”。

固定宽度报文中的文本字段还应先约定超长时怎么办。直接缩小缓冲区容量可能切开字符,盲目补大容量又可能破坏字段边界。可以在编码后检查实际字节数,超限时明确拒绝或采用协议规定的处理方式;不要让填充过程替业务决定该舍弃哪一部分文字。

排查时保留三个观测值:缓冲区字节长度、十六进制内容、按明确编码解出的文本。十六进制能解释边界发生在哪里,文本能说明展示结果,但两者都不能代替协议校验。示例只验证填充语义,没有测量性能,也没有用容错解码承诺数据完整。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。