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①

瀏覽器相容性

另見