JavaScriptのTextEncoder.encodeInto():用意したバッファーへUTF-8を書き込む
TextEncoder.encodeInto()で文字列を既存のバッファーへ書き込む方法を解説。日本語・絵文字のバイト数、容量不足への対応、ブラウザーとNode.jsの実行手順を学べます。
目的:文字列を決められた領域へ書き込む
バイナリーデータを作るときは、JavaScriptの文字列をバイト列へ変換する処理が必要です。例えば、先頭に管理情報、その後ろにメッセージを入れる形式では、あらかじめ用意した領域へ文字列を書き込みたくなります。
TextEncoder.encodeInto()は、渡したUint8Arrayへ文字列をUTF-8で書き込むメソッドです。戻り値から、入力をどこまで処理し、何バイト書き込んだかを確認できます。
この記事では、日本語と絵文字を含む文字列を、小さいバッファーへ分けて書き込む例を扱います。URLのパーセントエンコードとは用途が異なり、結果は数値で表されるバイト列です。
TextEncoderが扱う文字コードはUTF-8です。Shift_JISなどを指定して出力する機能はありません。MDNのTextEncoder
動作環境と実行方法
確認日は2026年9月29日です。ブラウザーとNode.jsの両方で利用できます。ただし、TextEncoderの存在だけでは、encodeInto()への対応を判断できません。
MDNではencodeInto()を、2021年4月以降、主要ブラウザー間で広く利用可能な機能としています。古いブラウザーや組み込みWebViewを対象にする場合は、後述のコードのようにメソッドの有無も確認してください。MDNの対応状況
Node.jsではencodeInto()がv12.11.0で追加されました。これは機能の導入バージョンです。この記事の実行環境にはNode.js 24系を使えます。グローバルのTextEncoderを利用するため、追加パッケージやimportは不要です。Node.jsのAPI資料
ブラウザーで試す
Chrome、Edge、Firefox、Safariなどの対応ブラウザーで開発者ツールのConsoleを開き、次節のJavaScriptを全体ごと実行します。DOMや通信を利用しないので、専用のHTMLファイルは必要ありません。
コードを即時実行関数で囲んでいるため、同じConsoleで繰り返し試せます。Consoleが最後に表示するundefinedは、記事のログとは別の評価結果です。
Node.jsで試す
次節のコードをUTF-8でencode-into-demo.jsに保存します。ターミナルで保存先のフォルダーを開き、次のコマンドを実行してください。
node --version
node encode-into-demo.js以下の出力は仕様に基づく期待値です。本記事の作成時にコードの実機実行は行っていません。
引数と戻り値を理解する
呼び出し方はencoder.encodeInto(source, destination)です。
source:変換する文字列です。destination:書き込み先のUint8Arrayです。既存の内容が書き換わります。- 戻り値の
read:処理した入力のUTF-16コード単位数です。 - 戻り値の
written:実際に書き込んだUTF-8のバイト数です。
戻り値はバイト配列ではなく、進捗を表すオブジェクトです。データ本体はdestinationに入ります。両方の数値の意味はNode.jsの引数・戻り値の説明でも確認できます。
入力位置と出力位置は別の単位
Aあ😀Bを考えます。JavaScriptのlengthでは、Aが1、あが1、😀が2、Bが1なので、合計は5です。一方、UTF-8ではそれぞれ1、3、4、1バイトとなり、全体で9バイト必要です。
このため、残りの文字列を取り出す位置にはread、書き込んだ領域を取り出す位置にはwrittenを使います。両者を入れ替えると、入力を飛ばしたり、不必要なバイトまで渡したりする原因になります。
自己完結したコードで部分書き込みを確認する
次の例は、5バイトの作業用バッファーを繰り返し利用します。各回で書き込んだ範囲だけを表示し、最後に処理量を確認します。
(() => {
if (
typeof TextEncoder === "undefined" ||
typeof TextEncoder.prototype.encodeInto !== "function"
) {
console.log("この環境はencodeInto()に対応していません");
return;
}
const encoder = new TextEncoder();
const source = "Aあ😀B";
const buffer = new Uint8Array(5);
let offset = 0;
let totalWritten = 0;
let round = 0;
console.log(`入力のlength=${source.length}`);
while (offset < source.length) {
const { read, written } = encoder.encodeInto(
source.slice(offset),
buffer
);
if (read === 0) {
throw new Error("バッファーが小さすぎて処理を進められません");
}
const bytes = Array.from(buffer.subarray(0, written));
round += 1;
console.log(
`${round}回目: read=${read}, written=${written}, ` +
`bytes=${JSON.stringify(bytes)}`
);
offset += read;
totalWritten += written;
}
console.log(`完了: read=${offset}, written=${totalWritten}`);
})();期待出力は次のとおりです。
入力のlength=5
1回目: read=2, written=4, bytes=[65,227,129,130]
2回目: read=3, written=5, bytes=[240,159,152,128,66]
完了: read=5, written=9最初はAあまで書き込みます。残りの容量は1バイトですが、次の絵文字に必要な4バイトを確保できないため、そこで止まります。UTF-8のバイト列を文字の途中まで書くことはありません。MDNの戻り値の説明
次はsource.slice(2)、つまり😀Bを渡します。絵文字がUTF-16では2コード単位なので、2回目のreadは3です。合計のreadが入力のlengthと一致すれば、入力をすべて処理できています。
このコードは同期処理です。Promiseやawaitは不要で、処理の順番もコードの記述どおりです。進捗がない場合のthrowは、無限ループを防ぐための意図的な停止です。実際のアプリでは呼び出し元で捕捉し、容量を変更するかエラーとして扱います。
失敗例と制約
容量不足は戻り値で検出する
容量が足りないこと自体は例外になりません。次のコードも単独で実行できます。対応環境で試してください。
(() => {
const encoder = new TextEncoder();
const buffer = new Uint8Array(2);
const { read, written } = encoder.encodeInto("あ", buffer);
console.log(`read=${read}, written=${written}`);
console.log(JSON.stringify(Array.from(buffer)));
})();期待出力です。
read=0, written=0
[0,0]あには3バイト必要なので、2バイトの領域では処理が進みません。同じ入力と同じ容量で再試行し続けても結果は変わりません。
先ほどのループでbufferの容量を2へ変更すると、最初はAだけ進み、その次に進捗チェックで停止します。容量不足を扱う設計では、「再試行する」だけでなく、「次の試行で何を変えるか」を決める必要があります。
バッファー全体を結果として扱わない
有効な出力範囲は先頭からwrittenバイトです。使い回したバッファーの残りには、以前のデータが残る可能性があります。送信や保存の対象は、この有効範囲に限定してください。
例ではログ用にArray.from()で数値をコピーしています。subarray()で切り出したビュー自体は元のメモリーを共有するため、後で使う目的で保存したままバッファーを上書きすると、保存したつもりの内容も変わります。非同期の送信処理へ渡すなら、処理が終わるまで再利用を待つか、独立した配列へコピーする設計が必要です。
文字列長だけで容量を決めない
new Uint8Array(source.length)では、日本語や絵文字を含む文字列を一度で格納できないことがあります。一括変換用にはsource.length * 3バイトが容量の上限目安になりますが、大きな入力ではメモリー消費も増えます。MDNの容量設計
また、UTF-8の文字境界を保つことと、人が一文字と感じるまとまりを保つことは別です。結合文字や複数のコードポイントで構成される絵文字を含む表示テキストの切り詰めには、そのまま流用しないでください。
活用事例:バイナリーデータ内の文字列領域
例えば、先頭2バイトを管理情報用に確保したデータ形式では、buffer.subarray(2)を出力先として渡せます。encodeInto()は、渡されたビューの先頭から書くため、元の配列の先頭2バイトを避けて配置できます。MDNの書き込み位置の指定
実装時には、次の方針を決めておくと扱いやすくなります。
- 全文が必要なデータでは、
readと入力のlengthを照合し、未完了なら領域を広げるか入力を拒否します。 - 分割可能なデータでは、今回の有効な出力を消費してから、未処理の入力へ進みます。
- バッファーを再利用する処理では、結果を使い終えるタイミングを明確にします。
最初の練習では、主例の容量を4や9に変え、各回のreadとwrittenを予想してみてください。入力の位置と出力の長さを別々に追えるようになると、バッファーを使う処理を組み立てやすくなります。
公式参考資料
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。