このページは、JavaScript でのバイナリデータの操作に関する入門です。Bun はバイナリデータを操作するための多くのデータ型とユーティリティを実装しており、そのほとんどは Web 標準です。Bun 固有の API はそのように明記されます。
以下は目次としても機能するクイック「チートシート」です。左側の列の項目をクリックすると、そのセクションに移動します。
| クラス | 説明 |
|---|---|
TypedArray | バイナリデータを操作するための Array ライクなインターフェースを提供するクラスファミリ。Uint8Array、Uint16Array、Int8Array などを含みます。 |
Buffer | 幅広い利便性メソッドを実装した Uint8Array のサブクラス。この表の他の要素とは異なり、これは Node.js API(Bun が実装)です。ブラウザでは使用できません。 |
DataView | 特定のバイトオフセットで ArrayBuffer に任意のバイト数を書き込むための get/set API を提供するクラス。バイナリプロトコルの読み取りや書き込みによく使用されます。 |
Blob | 通常ファイルを表す読み取り専用のバイナリデータブロブ。MIME type、size、および ArrayBuffer、ReadableStream、文字列への変換メソッドを持ちます。 |
File | ファイルを表す Blob のサブクラス。name と lastModified タイムスタンプを持ちます。Node.js v20 で実験的サポートがあります。 |
BunFile | Bun のみ。ディスク上の遅延読み込みファイルを表す Blob のサブクラス。Bun.file(path) で作成されます。 |
ArrayBuffer とビュー
2009 年まで、JavaScript にはバイナリデータを格納して操作するための言語ネイティブの方法はありませんでした。ECMAScript v5 はこれのために多くの新しいメカニズムを導入しました。最も基本的な構築要素は ArrayBuffer で、メモリ内のバイト列を表す単純なデータ構造です。
// このバッファーは 8 バイトを格納できます
const buf = new ArrayBuffer(8);名前にもかかわらず、これは配列ではなく、期待される配列メソッドや演算子のいずれもサポートしていません。実際、ArrayBuffer から値を直接読み書きする方法はありません。サイズを確認し、その「スライス」を作成する以外に、できることはほとんどありません。
const buf = new ArrayBuffer(8);
buf.byteLength; // => 8
const slice = buf.slice(0, 4); // 新しい ArrayBuffer を返す
slice.byteLength; // => 4何か面白いことを行うには、「ビュー」と呼ばれる構造が必要です。ビューは ArrayBuffer インスタンスを ラップ し、基盤となるデータを読み書きできるようにするクラスです。ビューには 2 つのタイプがあります。型付き配列 と DataView です。
DataView
DataView クラスは、ArrayBuffer 内のデータを読み書きするための低レベルなインターフェースです。
以下では、新しい DataView を作成し、最初のバイトを 3 に設定します。
const buf = new ArrayBuffer(4);
// [0b00000000, 0b00000000, 0b00000000, 0b00000000]
const dv = new DataView(buf);
dv.setUint8(0, 3); // バイトオフセット 0 に値 3 を書き込む
dv.getUint8(0); // => 3
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]次に、バイトオフセット 1 に Uint16 を書き込みます。これには 2 バイトが必要です。値 513 を使用しています。これは 2 * 256 + 1 です。バイトでは 00000010 00000001 です。
dv.setUint16(1, 513);
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]
console.log(dv.getUint16(1)); // => 513これで、基盤となる ArrayBuffer の最初の 3 バイトに値を割り当てました。2 番目と 3 番目のバイトは setUint16() を使用して作成されましたが、getUint8() を使用して各構成バイトを読み取ることができます。
console.log(dv.getUint8(1)); // => 2
console.log(dv.getUint8(2)); // => 1基盤となる ArrayBuffer で利用可能なスペースよりも多くのスペースを必要とする値を書き込もうとすると、エラーが発生します。以下では、Float64(8 バイトが必要)をバイトオフセット 0 に書き込もうとしていますが、バッファーには合計 4 バイトしかありません。
dv.setFloat64(0, 3.1415);
// ^ RangeError: Out of bounds accessDataView で利用可能なメソッドは次のとおりです。
TypedArray
型付き配列は、ArrayBuffer 内のデータを操作するための Array ライクなインターフェースを提供するクラスファミリです。DataView が特定のオフセットでさまざまなサイズの数字を書き込めるのに対し、TypedArray は基盤となるバイトを固定サイズの数字の配列として解釈します。
NOTE
このクラスファミリを、共通のスーパークラス `TypedArray` で総称するのは一般的です。このクラスは JavaScript に _内部的_ なものであり、直接インスタンスを作成することはできず、`TypedArray` はグローバルスコープで定義されていません。これは `interface` または抽象クラスとして考えてください。const buffer = new ArrayBuffer(3);
const arr = new Uint8Array(buffer);
// コンテンツはゼロに初期化されます
console.log(arr); // Uint8Array(3) [0, 0, 0]
// 配列のように値を割り当て
arr[0] = 0;
arr[1] = 10;
arr[2] = 255;
arr[3] = 255; // 何もしない、範囲外ArrayBuffer がバイトの汎用シーケンスであるのに対し、これらの型付き配列クラスはバイトを特定のバイトサイズの数字の配列として解釈します。 上の行には生のバイトが含まれており、後の行には異なる型付き配列クラスを使用して ビュー された場合にこれらのバイトがどのように解釈されるかが含まれています。
次のクラスは型付き配列で、ArrayBuffer 内のバイトをどのように解釈するかの説明と共に示されています。
| クラス | 説明 |
|---|---|
Uint8Array | 1 バイトごとに符号なし 8 ビット整数として解釈されます。範囲は 0 から 255。 |
Uint16Array | 2 バイトごとに符号なし 16 ビット整数として解釈されます。範囲は 0 から 65535。 |
Uint32Array | 4 バイトごとに符号なし 32 ビット整数として解釈されます。範囲は 0 から 4294967295。 |
Int8Array | 1 バイトごとに符号付き 8 ビット整数として解釈されます。範囲は -128 から 127。 |
Int16Array | 2 バイトごとに符号付き 16 ビット整数として解釈されます。範囲は -32768 から 32767。 |
Int32Array | 4 バイトごとに符号付き 32 ビット整数として解釈されます。範囲は -2147483648 から 2147483647。 |
Float16Array | 2 バイトごとに 16 ビット浮動小数点数として解釈されます。範囲は -6.104e5 から 6.55e4。 |
Float32Array | 4 バイトごとに 32 ビット浮動小数点数として解釈されます。範囲は -3.4e38 から 3.4e38。 |
Float64Array | 8 バイトごとに 64 ビット浮動小数点数として解釈されます。範囲は -1.7e308 から 1.7e308。 |
BigInt64Array | 8 バイトごとに符号付き BigInt として解釈されます。範囲は -9223372036854775808 から 9223372036854775807(ただし BigInt はより大きな数を表現できます)。 |
BigUint64Array | 8 バイトごとに符号なし BigInt として解釈されます。範囲は 0 から 18446744073709551615(ただし BigInt はより大きな数を表現できます)。 |
Uint8ClampedArray | Uint8Array と同じですが、要素に値を割り当てる際に 0-255 の範囲に自動的に「クランプ」されます。 |
以下の表は、ArrayBuffer 内のバイトが異なる型付き配列クラスを使用してビューされた場合にどのように解釈されるかを示しています。
| バイト 0 | バイト 1 | バイト 2 | バイト 3 | バイト 4 | バイト 5 | バイト 6 | バイト 7 | |
|---|---|---|---|---|---|---|---|---|
ArrayBuffer | 00000000 | 00000001 | 00000010 | 00000011 | 00000100 | 00000101 | 00000110 | 00000111 |
Uint8Array | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Uint16Array | 256 (1 * 256 + 0) | 770 (3 * 256 + 2) | 1284 (5 * 256 + 4) | 1798 (7 * 256 + 6) | ||||
Uint32Array | 50462976 | 117835012 | ||||||
BigUint64Array | 506097522914230528n |
事前定義された ArrayBuffer から型付き配列を作成するには:
// ArrayBuffer から型付き配列を作成
const buf = new ArrayBuffer(10);
const arr = new Uint8Array(buf);
arr[0] = 30;
arr[1] = 60;
// すべての要素はゼロに初期化されます
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];この同じ ArrayBuffer から Uint32Array をインスタンス化しようとすると、エラーが発生します。
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
// ^ RangeError: ArrayBuffer length minus the byteOffset
// is not a multiple of the element sizeUint32 値には 4 バイト(16 ビット)が必要です。ArrayBuffer は 10 バイトの長さであるため、その内容を 4 バイトのチャンクにきれいに分割する方法はありません。
これを修正するには、ArrayBuffer の特定の「スライス」に対して型付き配列を作成できます。以下の Uint16Array は、基盤となる ArrayBuffer の 最初の 8 バイトのみを「ビュー」します。これを実現するには、byteOffset を 0 に、length を 2 に指定します。これは、配列が保持する Uint32 数の数を示します。
// ArrayBuffer スライスから型付き配列を作成
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf, 0, 2);
/*
buf _ _ _ _ _ _ _ _ _ _ 10 バイト
arr [_______,_______] 2 つの 4 バイト要素
*/
arr.byteOffset; // 0
arr.length; // 2ArrayBuffer インスタンスを明示的に作成する必要はありません。型付き配列コンストラクターで長さを直接指定できます。
const arr2 = new Uint8Array(5);
// すべての要素はゼロに初期化されます
// => Uint8Array(5) [0, 0, 0, 0, 0]型付き配列は、数字の配列または別の型付き配列から直接インスタンス化することもできます。
// 数字の配列から
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
arr1[0]; // => 0;
arr1[7]; // => 7;
// 別の型付き配列から
const arr2 = new Uint8Array(arr);一般的に言って、型付き配列は通常の配列と同じメソッドを提供しますが、いくつかの例外があります。たとえば、push と pop は型付き配列では使用できません。基盤となる ArrayBuffer のサイズ変更が必要になるためです。
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
// 一般的な配列メソッドをサポート
arr.filter(n => n > 128); // Uint8Array(1) [255]
arr.map(n => n * 2); // Uint8Array(8) [0, 2, 4, 6, 8, 10, 12, 14]
arr.reduce((acc, n) => acc + n, 0); // 28
arr.forEach(n => console.log(n)); // 0 1 2 3 4 5 6 7
arr.every(n => n < 10); // true
arr.find(n => n > 5); // 6
arr.includes(5); // true
arr.indexOf(5); // 5型付き配列のプロパティとメソッドの詳細については、MDN ドキュメント を参照してください。
Uint8Array
特に Uint8Array を強調する価値があります。これは 0 から 255 の間の符号なし 8 ビット整数のシーケンスである古典的な「バイト配列」を表しているためです。これは JavaScript で最も一般的な型付き配列です。
Bun では、将来的に他の JavaScript エンジンでも、バイト配列と base64 または 16 進文字列としてのシリアライズされた表現の間で変換するためのメソッドが利用可能です。
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBA=="
Uint8Array.fromBase64("AQIDBA=="); // Uint8Array(4) [1, 2, 3, 4, 5]
new Uint8Array([255, 254, 253, 252, 251]).toHex(); // "fffefdfcfb"
Uint8Array.fromHex("fffefdfcfb"); // Uint8Array(5) [255, 254, 253, 252, 251]これは TextEncoder#encode の戻り値であり、TextDecoder#decode の入力タイプです。これらは文字列とさまざまなバイナリエンコーディング(最も注目すべきは "utf-8")を変換するように設計された 2 つのユーティリティクラスです。
const encoder = new TextEncoder();
const bytes = encoder.encode("hello world");
// => Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
const decoder = new TextDecoder();
const text = decoder.decode(bytes);
// => hello worldBuffer
Bun は Buffer を実装しています。これは JavaScript 仕様に型付き配列が導入される前に作成された、バイナリデータを操作するための Node.js API です。その後、Uint8Array のサブクラスとして再実装されました。配列のようなメソッドと DataView のようなメソッドを含む幅広いメソッドを提供します。
const buf = Buffer.from("hello world");
// => Buffer(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
buf.length; // => 11
buf[0]; // => 104, 'h' の ascii
buf.writeUInt8(72, 0); // => 'H' の ascii
console.log(buf.toString());
// => Hello world完全なドキュメントについては、Node.js ドキュメント を参照してください。
Blob
Blob はファイルを表すために一般的に使用される Web API です。Blob は当初ブラウザで実装されました(JavaScript 自体の一部である ArrayBuffer とは異なります)が、現在は Node と Bun でサポートされています。
Blob インスタンスを直接作成することは一般的ではありません。多くの場合、外部ソース(ブラウザの <input type="file"> 要素など)またはライブラリから Blob インスタンスを受け取ります。とはいえ、1 つ以上の文字列またはバイナリの「ブロブパーツ」から Blob を作成することは可能です。
const blob = new Blob(["<html>Hello</html>"], {
type: "text/html",
});
blob.type; // => text/html
blob.size; // => 19これらのパーツは string、ArrayBuffer、TypedArray、DataView、または他の Blob インスタンスにできます。ブロブパーツは提供された順序で連結されます。
const blob = new Blob([
"<html>",
new Blob(["<body>"]),
new Uint8Array([104, 101, 108, 108, 111]), // バイナリの "hello"
"</body></html>",
]);Blob の内容は、さまざまな形式で非同期に読み取ることができます。
await blob.text(); // => <html><body>hello</body></html>
await blob.bytes(); // => Uint8Array (内容をコピー)
await blob.arrayBuffer(); // => ArrayBuffer (内容をコピー)
await blob.stream(); // => ReadableStreamBunFile
BunFile は、ディスク上の遅延読み込みファイルを表すために使用される Blob のサブクラスです。File と同様に、name と lastModified プロパティを追加します。File とは異なり、ファイルをメモリに読み込む必要はありません。
const file = Bun.file("index.txt");
// => BunFileFile
File は Blob のサブクラスで、name と lastModified プロパティを追加します。これは、<input type="file"> 要素を介してアップロードされたファイルを表すためにブラウザで一般的に使用されます。Node.js と Bun は File を実装しています。
// ブラウザで!
// <input type="file" id="file" />
const files = document.getElementById("file").files;
// => File[]const file = new File(["<html>Hello</html>"], "index.html", {
type: "text/html",
});完全なドキュメント情報については、MDN ドキュメント を参照してください。
ストリーム
ストリームは、メモリにすべてを一度に読み込まずにバイナリデータを操作するための重要な抽象化です。ファイルの読み書き、ネットワークリクエストの送受信、大量のデータの処理に一般的に使用されます。
Bun は Web API である ReadableStream と WritableStream を実装しています。
NOTE
Bun は `node:stream` モジュールも実装しており、[`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams)、[`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams)、[`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams) を含みます。完全なドキュメントについては、Node.js ドキュメントを参照してください。シンプルな可読ストリームを作成するには:
const stream = new ReadableStream({
start(controller) {
controller.enqueue("hello");
controller.enqueue("world");
controller.close();
},
});このストリームの内容は、for await 構文でチャンクごとに読み取ることができます。
for await (const chunk of stream) {
console.log(chunk);
}
// => "hello"
// => "world"Bun のストリームのより完全な議論については、API > ストリーム を参照してください。
変換
あるバイナリ形式から別の形式への変換は一般的なタスクです。このセクションは参考として意図されています。
ArrayBuffer から
ArrayBuffer は TypedArray のような他のバイナリ構造の基盤となるデータを格納するため、以下のスニペットは ArrayBuffer から別の形式へ 変換 しているわけではありません。代わりに、基盤となるデータに格納されたデータを使用して新しいインスタンスを 作成 しています。
TypedArray へ
new Uint8Array(buf);DataView へ
new DataView(buf);Buffer へ
// 全 ArrayBuffer 上に Buffer を作成
Buffer.from(buf);
// ArrayBuffer のスライス上に Buffer を作成
Buffer.from(buf, 0, 10);string へ
UTF-8 として:
new TextDecoder().decode(buf);number[] へ
Array.from(new Uint8Array(buf));Blob へ
new Blob([buf], { type: "text/plain" });ReadableStream へ
次のスニペットは ReadableStream を作成し、全 ArrayBuffer を単一のチャンクとしてエンキューします。
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});チャンク分割
ArrayBuffer をチャンクでストリームするには、Uint8Array ビューを使用して各チャンクをエンキューします。
const view = new Uint8Array(buf);
const chunkSize = 1024;
new ReadableStream({
start(controller) {
for (let i = 0; i < view.length; i += chunkSize) {
controller.enqueue(view.slice(i, i + chunkSize));
}
controller.close();
},
});TypedArray から
ArrayBuffer へ
これは基盤となる ArrayBuffer を取得します。TypedArray は基盤となるバッファーの スライス のビューである可能性があるため、サイズが異なる場合があります。
arr.buffer;DataView へ
型付き配列と同じバイト範囲で DataView を作成します。
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);Buffer へ
Buffer.from(arr);string へ
UTF-8 として:
new TextDecoder().decode(arr);number[] へ
Array.from(arr);Blob へ
// arr が基盤となる型付き配列全体のビューである場合のみ
new Blob([arr.buffer], { type: "text/plain" });ReadableStream へ
new ReadableStream({
start(controller) {
controller.enqueue(arr);
controller.close();
},
});チャンク分割
ArrayBuffer をチャンクでストリームするには、TypedArray をチャンクに分割し、それぞれを個別にエンキューします。
new ReadableStream({
start(controller) {
for (let i = 0; i < arr.length; i += chunkSize) {
controller.enqueue(arr.slice(i, i + chunkSize));
}
controller.close();
},
});DataView から
ArrayBuffer へ
view.buffer;TypedArray へ
DataView の byteLength が TypedArray サブクラスの BYTES_PER_ELEMENT の倍数である場合にのみ機能します。
new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
new Uint16Array(view.buffer, view.byteOffset, view.byteLength / 2);
new Uint32Array(view.buffer, view.byteOffset, view.byteLength / 4);
// など...Buffer へ
Buffer.from(view.buffer, view.byteOffset, view.byteLength);string へ
UTF-8 として:
new TextDecoder().decode(view);number[] へ
Array.from(view);Blob へ
new Blob([view.buffer], { type: "text/plain" });ReadableStream へ
new ReadableStream({
start(controller) {
controller.enqueue(view.buffer);
controller.close();
},
});チャンク分割
ArrayBuffer をチャンクでストリームするには、DataView をチャンクに分割し、それぞれを個別にエンキューします。
new ReadableStream({
start(controller) {
for (let i = 0; i < view.byteLength; i += chunkSize) {
controller.enqueue(view.buffer.slice(i, i + chunkSize));
}
controller.close();
},
});Buffer から
ArrayBuffer へ
buf.buffer;TypedArray へ
new Uint8Array(buf);DataView へ
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);string へ
UTF-8 として:
buf.toString();base64 として:
buf.toString("base64");16 進として:
buf.toString("hex");number[] へ
Array.from(buf);Blob へ
new Blob([buf], { type: "text/plain" });ReadableStream へ
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});チャンク分割
ArrayBuffer をチャンクでストリームするには、Buffer をチャンクに分割し、それぞれを個別にエンキューします。
new ReadableStream({
start(controller) {
for (let i = 0; i < buf.length; i += chunkSize) {
controller.enqueue(buf.slice(i, i + chunkSize));
}
controller.close();
},
});Blob から
ArrayBuffer へ
Blob クラスはこの目的のための利便性メソッドを提供します。
await blob.arrayBuffer();TypedArray へ
await blob.bytes();DataView へ
new DataView(await blob.arrayBuffer());Buffer へ
Buffer.from(await blob.arrayBuffer());string へ
UTF-8 として:
await blob.text();number[] へ
Array.from(await blob.bytes());ReadableStream へ
blob.stream();ReadableStream から
ReadableStream を他の形式に変換しやすくするために、Response を便利な中間表現として使用するのが一般的です。
stream; // ReadableStream
const buffer = new Response(stream).arrayBuffer();ただし、このアプローチは冗長で、全体的なパフォーマンスを不必要に遅くするオーバーヘッドを追加します。Bun は ReadableStream をさまざまなバイナリ形式に変換するための最適化された利便性関数を実装しています。
ArrayBuffer へ
// Response を使用
new Response(stream).arrayBuffer();
// Bun 関数を使用
Bun.readableStreamToArrayBuffer(stream);Uint8Array へ
// Response を使用
new Response(stream).bytes();
// Bun 関数を使用
Bun.readableStreamToBytes(stream);TypedArray へ
// Response を使用
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);
// Bun 関数を使用
new Int8Array(Bun.readableStreamToArrayBuffer(stream));DataView へ
// Response を使用
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);
// Bun 関数を使用
new DataView(Bun.readableStreamToArrayBuffer(stream));Buffer へ
// Response を使用
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);
// Bun 関数を使用
Buffer.from(Bun.readableStreamToArrayBuffer(stream));string へ
UTF-8 として:
// Response を使用
await new Response(stream).text();
// Bun 関数を使用
await Bun.readableStreamToText(stream);number[] へ
// Response を使用
const arr = await new Response(stream).bytes();
Array.from(arr);
// Bun 関数を使用
Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream)));Bun は ReadableStream をそのチャンクの配列に解決するためのユーティリティを提供します。各チャンクは文字列、型付き配列、または ArrayBuffer である場合があります。
// Bun 関数を使用
Bun.readableStreamToArray(stream);Blob へ
new Response(stream).blob();ReadableStream へ
独立して消費できる 2 つのストリームに ReadableStream を分割するには:
const [a, b] = stream.tee();