Skip to content

このページは、JavaScript でのバイナリデータの操作に関する入門です。Bun はバイナリデータを操作するための多くのデータ型とユーティリティを実装しており、そのほとんどは Web 標準です。Bun 固有の API はそのように明記されます。

以下は目次としても機能するクイック「チートシート」です。左側の列の項目をクリックすると、そのセクションに移動します。

クラス説明
TypedArrayバイナリデータを操作するための Array ライクなインターフェースを提供するクラスファミリ。Uint8ArrayUint16ArrayInt8Array などを含みます。
Buffer幅広い利便性メソッドを実装した Uint8Array のサブクラス。この表の他の要素とは異なり、これは Node.js API(Bun が実装)です。ブラウザでは使用できません。
DataView特定のバイトオフセットで ArrayBuffer に任意のバイト数を書き込むための get/set API を提供するクラス。バイナリプロトコルの読み取りや書き込みによく使用されます。
Blob通常ファイルを表す読み取り専用のバイナリデータブロブ。MIME typesize、および ArrayBufferReadableStream、文字列への変換メソッドを持ちます。
Fileファイルを表す Blob のサブクラス。namelastModified タイムスタンプを持ちます。Node.js v20 で実験的サポートがあります。
BunFileBun のみ。ディスク上の遅延読み込みファイルを表す Blob のサブクラス。Bun.file(path) で作成されます。

ArrayBuffer とビュー

2009 年まで、JavaScript にはバイナリデータを格納して操作するための言語ネイティブの方法はありませんでした。ECMAScript v5 はこれのために多くの新しいメカニズムを導入しました。最も基本的な構築要素は ArrayBuffer で、メモリ内のバイト列を表す単純なデータ構造です。

ts
// このバッファーは 8 バイトを格納できます
const buf = new ArrayBuffer(8);

名前にもかかわらず、これは配列ではなく、期待される配列メソッドや演算子のいずれもサポートしていません。実際、ArrayBuffer から値を直接読み書きする方法はありません。サイズを確認し、その「スライス」を作成する以外に、できることはほとんどありません。

ts
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 に設定します。

ts
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]

次に、バイトオフセット 1Uint16 を書き込みます。これには 2 バイトが必要です。値 513 を使用しています。これは 2 * 256 + 1 です。バイトでは 00000010 00000001 です。

ts
dv.setUint16(1, 513);
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]

console.log(dv.getUint16(1)); // => 513

これで、基盤となる ArrayBuffer の最初の 3 バイトに値を割り当てました。2 番目と 3 番目のバイトは setUint16() を使用して作成されましたが、getUint8() を使用して各構成バイトを読み取ることができます。

ts
console.log(dv.getUint8(1)); // => 2
console.log(dv.getUint8(2)); // => 1

基盤となる ArrayBuffer で利用可能なスペースよりも多くのスペースを必要とする値を書き込もうとすると、エラーが発生します。以下では、Float64(8 バイトが必要)をバイトオフセット 0 に書き込もうとしていますが、バッファーには合計 4 バイトしかありません。

ts
dv.setFloat64(0, 3.1415);
// ^ RangeError: Out of bounds access

DataView で利用可能なメソッドは次のとおりです。

ゲッターセッター
getBigInt64()setBigInt64()
getBigUint64()setBigUint64()
getFloat32()setFloat32()
getFloat64()setFloat64()
getInt16()setInt16()
getInt32()setInt32()
getInt8()setInt8()
getUint16()setUint16()
getUint32()setUint32()
getUint8()setUint8()

TypedArray

型付き配列は、ArrayBuffer 内のデータを操作するための Array ライクなインターフェースを提供するクラスファミリです。DataView が特定のオフセットでさまざまなサイズの数字を書き込めるのに対し、TypedArray は基盤となるバイトを固定サイズの数字の配列として解釈します。

NOTE

このクラスファミリを、共通のスーパークラス `TypedArray` で総称するのは一般的です。このクラスは JavaScript に _内部的_ なものであり、直接インスタンスを作成することはできず、`TypedArray` はグローバルスコープで定義されていません。これは `interface` または抽象クラスとして考えてください。
ts
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 内のバイトをどのように解釈するかの説明と共に示されています。

クラス説明
Uint8Array1 バイトごとに符号なし 8 ビット整数として解釈されます。範囲は 0 から 255。
Uint16Array2 バイトごとに符号なし 16 ビット整数として解釈されます。範囲は 0 から 65535。
Uint32Array4 バイトごとに符号なし 32 ビット整数として解釈されます。範囲は 0 から 4294967295。
Int8Array1 バイトごとに符号付き 8 ビット整数として解釈されます。範囲は -128 から 127。
Int16Array2 バイトごとに符号付き 16 ビット整数として解釈されます。範囲は -32768 から 32767。
Int32Array4 バイトごとに符号付き 32 ビット整数として解釈されます。範囲は -2147483648 から 2147483647。
Float16Array2 バイトごとに 16 ビット浮動小数点数として解釈されます。範囲は -6.104e5 から 6.55e4。
Float32Array4 バイトごとに 32 ビット浮動小数点数として解釈されます。範囲は -3.4e38 から 3.4e38。
Float64Array8 バイトごとに 64 ビット浮動小数点数として解釈されます。範囲は -1.7e308 から 1.7e308。
BigInt64Array8 バイトごとに符号付き BigInt として解釈されます。範囲は -9223372036854775808 から 9223372036854775807(ただし BigInt はより大きな数を表現できます)。
BigUint64Array8 バイトごとに符号なし BigInt として解釈されます。範囲は 0 から 18446744073709551615(ただし BigInt はより大きな数を表現できます)。
Uint8ClampedArrayUint8Array と同じですが、要素に値を割り当てる際に 0-255 の範囲に自動的に「クランプ」されます。

以下の表は、ArrayBuffer 内のバイトが異なる型付き配列クラスを使用してビューされた場合にどのように解釈されるかを示しています。

バイト 0バイト 1バイト 2バイト 3バイト 4バイト 5バイト 6バイト 7
ArrayBuffer0000000000000001000000100000001100000100000001010000011000000111
Uint8Array01234567
Uint16Array256 (1 * 256 + 0)770 (3 * 256 + 2)1284 (5 * 256 + 4)1798 (7 * 256 + 6)
Uint32Array50462976117835012
BigUint64Array506097522914230528n

事前定義された ArrayBuffer から型付き配列を作成するには:

ts
// 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 をインスタンス化しようとすると、エラーが発生します。

ts
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
//          ^  RangeError: ArrayBuffer length minus the byteOffset
//             is not a multiple of the element size

Uint32 値には 4 バイト(16 ビット)が必要です。ArrayBuffer は 10 バイトの長さであるため、その内容を 4 バイトのチャンクにきれいに分割する方法はありません。

これを修正するには、ArrayBuffer の特定の「スライス」に対して型付き配列を作成できます。以下の Uint16Array は、基盤となる ArrayBuffer最初の 8 バイトのみを「ビュー」します。これを実現するには、byteOffset0 に、length2 に指定します。これは、配列が保持する Uint32 数の数を示します。

ts
// ArrayBuffer スライスから型付き配列を作成
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf, 0, 2);

/*
  buf    _ _ _ _ _ _ _ _ _ _    10 バイト
  arr   [_______,_______]       2 つの 4 バイト要素
*/

arr.byteOffset; // 0
arr.length; // 2

ArrayBuffer インスタンスを明示的に作成する必要はありません。型付き配列コンストラクターで長さを直接指定できます。

ts
const arr2 = new Uint8Array(5);

// すべての要素はゼロに初期化されます
// => Uint8Array(5) [0, 0, 0, 0, 0]

型付き配列は、数字の配列または別の型付き配列から直接インスタンス化することもできます。

ts
// 数字の配列から
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
arr1[0]; // => 0;
arr1[7]; // => 7;

// 別の型付き配列から
const arr2 = new Uint8Array(arr);

一般的に言って、型付き配列は通常の配列と同じメソッドを提供しますが、いくつかの例外があります。たとえば、pushpop は型付き配列では使用できません。基盤となる ArrayBuffer のサイズ変更が必要になるためです。

ts
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 進文字列としてのシリアライズされた表現の間で変換するためのメソッドが利用可能です。

ts
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 つのユーティリティクラスです。

ts
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 world

Buffer

Bun は Buffer を実装しています。これは JavaScript 仕様に型付き配列が導入される前に作成された、バイナリデータを操作するための Node.js API です。その後、Uint8Array のサブクラスとして再実装されました。配列のようなメソッドと DataView のようなメソッドを含む幅広いメソッドを提供します。

ts
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 を作成することは可能です。

ts
const blob = new Blob(["<html>Hello</html>"], {
  type: "text/html",
});

blob.type; // => text/html
blob.size; // => 19

これらのパーツは stringArrayBufferTypedArrayDataView、または他の Blob インスタンスにできます。ブロブパーツは提供された順序で連結されます。

ts
const blob = new Blob([
  "<html>",
  new Blob(["<body>"]),
  new Uint8Array([104, 101, 108, 108, 111]), // バイナリの "hello"
  "</body></html>",
]);

Blob の内容は、さまざまな形式で非同期に読み取ることができます。

ts
await blob.text(); // => <html><body>hello</body></html>
await blob.bytes(); // => Uint8Array (内容をコピー)
await blob.arrayBuffer(); // => ArrayBuffer (内容をコピー)
await blob.stream(); // => ReadableStream

BunFile

BunFile は、ディスク上の遅延読み込みファイルを表すために使用される Blob のサブクラスです。File と同様に、namelastModified プロパティを追加します。File とは異なり、ファイルをメモリに読み込む必要はありません。

ts
const file = Bun.file("index.txt");
// => BunFile

File

FileBlob のサブクラスで、namelastModified プロパティを追加します。これは、<input type="file"> 要素を介してアップロードされたファイルを表すためにブラウザで一般的に使用されます。Node.js と Bun は File を実装しています。

ts
// ブラウザで!
// <input type="file" id="file" />

const files = document.getElementById("file").files;
// => File[]
ts
const file = new File(["<html>Hello</html>"], "index.html", {
  type: "text/html",
});

完全なドキュメント情報については、MDN ドキュメント を参照してください。


ストリーム

ストリームは、メモリにすべてを一度に読み込まずにバイナリデータを操作するための重要な抽象化です。ファイルの読み書き、ネットワークリクエストの送受信、大量のデータの処理に一般的に使用されます。

Bun は Web API である ReadableStreamWritableStream を実装しています。

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 ドキュメントを参照してください。

シンプルな可読ストリームを作成するには:

ts
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("hello");
    controller.enqueue("world");
    controller.close();
  },
});

このストリームの内容は、for await 構文でチャンクごとに読み取ることができます。

ts
for await (const chunk of stream) {
  console.log(chunk);
}

// => "hello"
// => "world"

Bun のストリームのより完全な議論については、API > ストリーム を参照してください。


変換

あるバイナリ形式から別の形式への変換は一般的なタスクです。このセクションは参考として意図されています。

ArrayBuffer から

ArrayBufferTypedArray のような他のバイナリ構造の基盤となるデータを格納するため、以下のスニペットは ArrayBuffer から別の形式へ 変換 しているわけではありません。代わりに、基盤となるデータに格納されたデータを使用して新しいインスタンスを 作成 しています。

TypedArray

ts
new Uint8Array(buf);

DataView

ts
new DataView(buf);

Buffer

ts
// 全 ArrayBuffer 上に Buffer を作成
Buffer.from(buf);

// ArrayBuffer のスライス上に Buffer を作成
Buffer.from(buf, 0, 10);

string

UTF-8 として:

ts
new TextDecoder().decode(buf);

number[]

ts
Array.from(new Uint8Array(buf));

Blob

ts
new Blob([buf], { type: "text/plain" });

ReadableStream

次のスニペットは ReadableStream を作成し、全 ArrayBuffer を単一のチャンクとしてエンキューします。

ts
new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});

チャンク分割

ArrayBuffer をチャンクでストリームするには、Uint8Array ビューを使用して各チャンクをエンキューします。

ts
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 は基盤となるバッファーの スライス のビューである可能性があるため、サイズが異なる場合があります。

ts
arr.buffer;

DataView

型付き配列と同じバイト範囲で DataView を作成します。

ts
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);

Buffer

ts
Buffer.from(arr);

string

UTF-8 として:

ts
new TextDecoder().decode(arr);

number[]

ts
Array.from(arr);

Blob

ts
// arr が基盤となる型付き配列全体のビューである場合のみ
new Blob([arr.buffer], { type: "text/plain" });

ReadableStream

ts
new ReadableStream({
  start(controller) {
    controller.enqueue(arr);
    controller.close();
  },
});

チャンク分割

ArrayBuffer をチャンクでストリームするには、TypedArray をチャンクに分割し、それぞれを個別にエンキューします。

ts
new ReadableStream({
  start(controller) {
    for (let i = 0; i < arr.length; i += chunkSize) {
      controller.enqueue(arr.slice(i, i + chunkSize));
    }
    controller.close();
  },
});

DataView から

ArrayBuffer

ts
view.buffer;

TypedArray

DataViewbyteLengthTypedArray サブクラスの BYTES_PER_ELEMENT の倍数である場合にのみ機能します。

ts
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

ts
Buffer.from(view.buffer, view.byteOffset, view.byteLength);

string

UTF-8 として:

ts
new TextDecoder().decode(view);

number[]

ts
Array.from(view);

Blob

ts
new Blob([view.buffer], { type: "text/plain" });

ReadableStream

ts
new ReadableStream({
  start(controller) {
    controller.enqueue(view.buffer);
    controller.close();
  },
});

チャンク分割

ArrayBuffer をチャンクでストリームするには、DataView をチャンクに分割し、それぞれを個別にエンキューします。

ts
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

ts
buf.buffer;

TypedArray

ts
new Uint8Array(buf);

DataView

ts
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);

string

UTF-8 として:

ts
buf.toString();

base64 として:

ts
buf.toString("base64");

16 進として:

ts
buf.toString("hex");

number[]

ts
Array.from(buf);

Blob

ts
new Blob([buf], { type: "text/plain" });

ReadableStream

ts
new ReadableStream({
  start(controller) {
    controller.enqueue(buf);
    controller.close();
  },
});

チャンク分割

ArrayBuffer をチャンクでストリームするには、Buffer をチャンクに分割し、それぞれを個別にエンキューします。

ts
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 クラスはこの目的のための利便性メソッドを提供します。

ts
await blob.arrayBuffer();

TypedArray

ts
await blob.bytes();

DataView

ts
new DataView(await blob.arrayBuffer());

Buffer

ts
Buffer.from(await blob.arrayBuffer());

string

UTF-8 として:

ts
await blob.text();

number[]

ts
Array.from(await blob.bytes());

ReadableStream

ts
blob.stream();

ReadableStream から

ReadableStream を他の形式に変換しやすくするために、Response を便利な中間表現として使用するのが一般的です。

ts
stream; // ReadableStream

const buffer = new Response(stream).arrayBuffer();

ただし、このアプローチは冗長で、全体的なパフォーマンスを不必要に遅くするオーバーヘッドを追加します。Bun は ReadableStream をさまざまなバイナリ形式に変換するための最適化された利便性関数を実装しています。

ArrayBuffer

ts
// Response を使用
new Response(stream).arrayBuffer();

// Bun 関数を使用
Bun.readableStreamToArrayBuffer(stream);

Uint8Array

ts
// Response を使用
new Response(stream).bytes();

// Bun 関数を使用
Bun.readableStreamToBytes(stream);

TypedArray

ts
// Response を使用
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);

// Bun 関数を使用
new Int8Array(Bun.readableStreamToArrayBuffer(stream));

DataView

ts
// Response を使用
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);

// Bun 関数を使用
new DataView(Bun.readableStreamToArrayBuffer(stream));

Buffer

ts
// Response を使用
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);

// Bun 関数を使用
Buffer.from(Bun.readableStreamToArrayBuffer(stream));

string

UTF-8 として:

ts
// Response を使用
await new Response(stream).text();

// Bun 関数を使用
await Bun.readableStreamToText(stream);

number[]

ts
// Response を使用
const arr = await new Response(stream).bytes();
Array.from(arr);

// Bun 関数を使用
Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream)));

Bun は ReadableStream をそのチャンクの配列に解決するためのユーティリティを提供します。各チャンクは文字列、型付き配列、または ArrayBuffer である場合があります。

ts
// Bun 関数を使用
Bun.readableStreamToArray(stream);

Blob

ts
new Response(stream).blob();

ReadableStream

独立して消費できる 2 つのストリームに ReadableStream を分割するには:

ts
const [a, b] = stream.tee();

Bun by www.bunjs.com.cn 編集