TextEncoder: encodeInto() 方法

Baseline 已广泛支持

此特性已得到良好支持,可在多种设备和浏览器版本上使用。自 2021 年 4 月起,所有浏览器均已支持此特性。

注意:此功能在 Web Workers 中可用。

TextEncoder.encodeInto() 方法接受一个待编码的字符串和一个目标 Uint8Array,将结果 UTF-8 编码文本放入其中,并返回一个指示编码进度的对象。这比 encode() 方法可能更高效——尤其当目标缓冲区是 Wasm 堆的视图时。

语法

js
encodeInto(string, uint8Array)

参数

string

包含待编码文本的字符串。

uint8Array

一个 Uint8Array 对象实例,用于存放结果的 UTF-8 编码文本。

返回值

一个对象,包含两个成员

read

已成功转换为 UTF-8 的源字符串的 UTF-16 码单元数量。如果 uint8Array 空间不足,此值可能小于 string.length

written

目标 Uint8Array 中已修改的字节数。写入的字节保证构成完整的 UTF-8 字节序列。

编码到特定位置

encodeInto() 始终将输出放在数组的开头。然而,有时希望输出从特定索引开始。解决方案是使用 TypedArray.prototype.subarray()

js
const encoder = new TextEncoder();

function encodeIntoAtPosition(string, u8array, position) {
  return encoder.encodeInto(
    string,
    position ? u8array.subarray(position | 0) : u8array,
  );
}

const u8array = new Uint8Array(8);
encodeIntoAtPosition("hello", u8array, 2);
console.log(u8array.join()); // 0,0,104,101,108,108,111,0

缓冲区大小

要转换 JavaScript 字符串 s,完全转换所需的输出空间绝不会小于 s.length 字节,也绝不会大于 s.length * 3 字节。字符串的 UTF-8 与 UTF-16 确切长度比取决于你正在处理的语言。

  • 对于主要使用 ASCII 字符的英文文本,比例接近 1。
  • 对于使用 U+0080 至 U+07FF 字符的脚本(包括希腊语、西里尔语、希伯来语、阿拉伯语等),比例约为 2。
  • 对于使用 U+0800 至 U+FFFF 字符的脚本(包括中文、日文、韩文等),比例约为 3。
  • 很少有整个脚本完全由 非 BMP 字符组成(尽管它们确实存在)。这些字符通常是数学符号、表情符号、历史脚本等。这些字符的比例是 2,因为它们在 UTF-8 中占用 4 个字节,在 UTF-16 中占用 2 个字节。

如果输出分配(通常在 Wasm 堆内)预期是短暂的,那么分配 s.length * 3 字节作为输出是有意义的,这样第一次转换就保证能转换整个字符串。

例如,如果你的文本主要是英文,长文本不太可能超过 s.length * 2 字节。因此,更乐观的方法可能是分配 s.length * 2 + 5 字节,并在乐观预测错误的情况下进行重新分配。

如果输出预期是长期的,那么计算最小分配 roundUpToBucketSize(s.length),最大分配大小 s.length * 3,并选择一个阈值 t(在内存使用和速度之间权衡),使得如果 roundUpToBucketSize(s.length) + t >= s.length * 3,则分配 s.length * 3 字节。否则,先分配 roundUpToBucketSize(s.length) 字节并进行转换。如果返回字典中的 read 项等于 s.length,则转换完成。否则,将目标缓冲区重新分配为 written + (s.length - read) * 3,然后通过获取从索引 read 开始的 s 的子字符串和从索引 written 开始的目标缓冲区的子缓冲区来转换剩余部分。

上述 roundUpToBucketSize() 是一个将参数向上舍入到分配器桶大小的函数。例如,如果已知你的 Wasm 分配器使用 2 的幂次方作为桶大小,那么 roundUpToBucketSize() 应该返回参数本身(如果它是 2 的幂次方),否则返回下一个 2 的幂次方。如果 Wasm 分配器的行为未知,roundUpToBucketSize() 应该是一个恒等函数。

如果你的分配器行为未知,你可能需要最多进行两次重新分配,并且让第一次重新分配将剩余未转换的长度乘以二而不是三。然而,在这种情况下,不实现通常将已写入缓冲区长度乘以二的做法是有意义的,因为在那种情况下,如果发生第二次重新分配,与原始长度乘以三相比,它总是会过度分配。上述建议假设你不需要为零终止符分配空间。也就是说,在 Wasm 端你使用的是 Rust 字符串或非零终止的 C++ 类。如果你使用的是 C++ std::string,即使逻辑长度对你可见,在计算向上舍入到分配器桶大小时,你也需要考虑额外的终止符字节。请参阅下一节关于 C 字符串的内容。

无零终止符

如果输入字符串包含字符 U+0000,encodeInto() 将在输出中写入一个 0x00 字节。encodeInto() *不会*在逻辑输出之后写入 C 风格的 0x00 哨兵字节。

如果你的 Wasm 程序使用 C 字符串,那么写入 0x00 哨兵字节是你的责任,并且你无法阻止你的 Wasm 程序在 JavaScript 字符串包含 U+0000 时看到逻辑上截断的字符串。请注意:

js
const encoder = new TextEncoder();

function encodeIntoWithSentinel(string, u8array, position) {
  const stats = encoder.encodeInto(
    string,
    position ? u8array.subarray(position | 0) : u8array,
  );
  if (stats.written < u8array.length) u8array[stats.written] = 0; // append null if room
  return stats;
}

示例

编码到缓冲区

html
<p class="source">This is a sample paragraph.</p>
<p class="result"></p>
js
const sourcePara = document.querySelector(".source");
const resultPara = document.querySelector(".result");
const string = sourcePara.textContent;

const textEncoder = new TextEncoder();
const utf8 = new Uint8Array(string.length);

const encodedResults = textEncoder.encodeInto(string, utf8);
resultPara.textContent +=
  `Bytes read: ${encodedResults.read}` +
  ` | Bytes written: ${encodedResults.written}` +
  ` | Encoded result: ${utf8}`;

规范

规范
编码
# ref-for-dom-textencoder-encodeinto①

浏览器兼容性

另见