TypeScriptにおける`TypedArray`

TypeScriptにおける`TypedArray`

この記事ではTypeScriptにおけるTypedArrayについて説明します。

TypeScriptにおけるTypedArrayについて実際的なサンプルを含めて解説します。

YouTube Video

TypeScriptにおけるTypedArray

TypedArrayは、バイナリデータを効率的に扱うための仕組みです。大きな画像データ、ネットワークのバイト列、WebGL用の数値配列など、低レベルのバイナリ操作で特に役立ちます。

ArrayBuffer の作り方

ArrayBufferは固定長の「バイト領域」を表します。まずはバッファを作って、そのサイズやバイト長を確認します。

1// Create an ArrayBuffer of 16 bytes
2const buffer: ArrayBuffer = new ArrayBuffer(16);
3console.log("buffer.byteLength:", buffer.byteLength); // 16
  • このコードは16バイトの空の領域を作ります。ArrayBuffer自体には読み書き機能がなく、TypedArrayDataViewでアクセスします。

TypedArrayの種類

TypedArrayには以下のような多くの種類があります。これらは扱うデータ型やサイズによって異なります。

TypedArray データ型 ビット数
Int8Array 8ビット整数 8ビット
Uint8Array 符号なし8ビット整数 8ビット
Uint8ClampedArray クランプされた符号なし8ビット整数 8ビット
Int16Array 16ビット整数 16ビット
Uint16Array 符号なし16ビット整数 16ビット
Int32Array 32ビット整数 32ビット
Uint32Array 符号なし32ビット整数 32ビット
Float32Array 32ビット浮動小数点数 32ビット
Float64Array 64ビット浮動小数点数 64ビット

基本的なTypedArrayUint8Array, Int16Array, Float32Array

TypedArrayArrayBuffer上に「型付きのビュー」を作るものです。以下はいくつかの代表的なTypedArrayを作って使う例です。

 1// Create a buffer and different typed views over it
 2const buf = new ArrayBuffer(8); // 8 bytes
 3
 4// Create views
 5const u8 = new Uint8Array(buf);    // 8 x uint8
 6const i16 = new Int16Array(buf);   // 4 x int16 (since each int16 is 2 bytes)
 7const f32 = new Float32Array(buf); // 2 x float32 (4 bytes each)
 8
 9console.log("Uint8 length:", u8.length);
10console.log("Int16 length:", i16.length);
11console.log("Float32 length:", f32.length);
  • 同じArrayBufferに複数のビューを作ることで、同じメモリを異なる型や粒度で読み書きできます。ビューのlengthは要素数で、byteLengthはバイト数です。

書き込みと読み取り(バイト単位の操作)

TypedArrayに値を書き込むと、メモリ上の該当バイトが更新されます。同じバッファを別のビューで読むと変化が見えます。

 1// Demonstrate writing via one view and reading via another
 2const buffer2 = new ArrayBuffer(4);
 3const u8view = new Uint8Array(buffer2);
 4const u32view = new Uint32Array(buffer2);
 5
 6u8view[0] = 0x78;
 7u8view[1] = 0x56;
 8u8view[2] = 0x34;
 9u8view[3] = 0x12;
10
11console.log("Uint8 bytes:", Array.from(u8view)); // [120, 86, 52, 18]
12console.log("Uint32 value (platform endianness):", u32view[0]); // value depends on endianness
  • この例ではバイト列を書き込んでから、32ビット整数として同じ領域を読み出します。出力は実行環境のエンディアンに依存する点に注意してください。

Endianness(バイト順)とDataView

環境依存のエンディアン問題を制御したい場合、DataViewが便利です。DataViewは読み書き時にエンディアンを指定できます。

 1// Use DataView to read/write with explicit endianness control
 2const b = new ArrayBuffer(4);
 3const dv = new DataView(b);
 4
 5// write little-endian 32-bit integer
 6dv.setUint32(0, 0x12345678, true);
 7
 8// read as little-endian and big-endian
 9const little = dv.getUint32(0, true);
10const big = dv.getUint32(0, false);
11
12console.log("little-endian read:", little.toString(16)); // "12345678"
13console.log("big-endian read:", big.toString(16));       // different value
  • DataViewは、バイト単位で細かく読み書きできる便利な仕組みです。符号付き・符号なしの整数や浮動小数などさまざまな型を扱えるほか、エンディアン(バイト順)を明示して指定できるため、バイナリ形式のプロトコルを実装する際にとても役立ちます。

subarray と slice の違い

TypedArraysubarrayは元のバッファを共有するビューを返し、sliceは新しいコピーを返します。どちらを使うかでパフォーマンスと副作用が変わります。

1const base = new Uint8Array([1, 2, 3, 4, 5]);
2
3const shared = base.subarray(1, 4); // shares underlying buffer
4const copied = base.slice(1, 4);    // copies data
5
6shared[0] = 99;
7console.log("base after shared modification:", base); // shows change
8console.log("copied remains:", copied);              // unaffected
  • 共有ビューで変更すると元の配列が変わるため、意図しない副作用が発生する可能性があります。元の配列を安全に保持したい場合は、事前に slice() でコピーを作成できます。

バッファのコピー・型変換(TypedArray間の変換)

TypedArray同士でデータをコピーする方法や、setメソッドで部分的に貼り付ける方法を説明します。

 1// Copy and convert between typed arrays
 2const src = new Float32Array([1.5, 2.5, 3.5]);
 3const dst = new Uint16Array(src.length);
 4
 5// Convert by mapping (explicit conversion)
 6for (let i = 0; i < src.length; i++) {
 7  dst[i] = Math.round(src[i]); // simple conversion rule
 8}
 9console.log("converted dst:", Array.from(dst)); // [2, 2, 4]
10
11// Using set to copy bytes (requires compatible underlying buffer or same element type)
12const src2 = new Uint8Array([10, 20, 30]);
13const dst2 = new Uint8Array(6);
14dst2.set(src2, 2); // copy src2 into dst2 starting at index 2
15console.log("dst2 after set:", Array.from(dst2)); // [0,0,10,20,30,0]
  • 要素の型が異なる場合は単純コピーではなく、丸めや範囲チェックなど値の変換を行う必要があります。setは同じ要素型のTypedArray間で高速にコピーできます。

応用例:バイナリプロトコルのパーサー(簡単な実装)

ここでは、1バイトのタイプ値、2バイトのデータ長、その後に続くペイロードといった固定された形式のバイナリデータを読み取る、シンプルなパーサーの例を紹介します。

 1// Simple binary message parser:
 2// format: [type: uint8][length: uint16 BE][payload: length bytes]
 3function parseMessages(buffer: ArrayBuffer) {
 4  const dv = new DataView(buffer);
 5  let offset = 0;
 6  const messages: { type: number; payload: Uint8Array }[] = [];
 7
 8  while (offset + 3 <= dv.byteLength) {
 9    const type = dv.getUint8(offset);
10    const length = dv.getUint16(offset + 1, false); // big-endian
11    offset += 3;
12    if (offset + length > dv.byteLength) break; // truncated
13    const payload = new Uint8Array(buffer, offset, length);
14    messages.push({ type, payload });
15    offset += length;
16  }
17
18  return messages;
19}
20
21// Example usage
22const buf = new ArrayBuffer(1 + 2 + 3 + 1 + 2 + 2); // two messages
23const dv = new DataView(buf);
24let off = 0;
25// first message: type=1, length=3, payload=[1,2,3]
26dv.setUint8(off, 1); dv.setUint16(off + 1, 3, false); off += 3;
27new Uint8Array(buf, off, 3).set([1, 2, 3]); off += 3;
28// second message: type=2, length=2, payload=[9,8]
29dv.setUint8(off, 2); dv.setUint16(off + 1, 2, false); off += 3;
30new Uint8Array(buf, off, 2).set([9, 8]);
31
32console.log(parseMessages(buf));
  • このサンプルはDataViewでヘッダを読み、Uint8Arrayでペイロードのスライスを作成しています。エンディアンとバッファ長チェックが重要です。

Web API と TypedArray(例:fetchでバイナリ取得)

ネットワークリクエストで得たArrayBufferTypedArrayで扱う典型例です。

 1// Example of fetching binary and mapping to typed array
 2async function fetchBinary(url: string) {
 3  const res = await fetch(url);
 4  const ab = await res.arrayBuffer();
 5  const view = new Uint8Array(ab);
 6  // process view...
 7  console.log("received bytes:", view.length);
 8  return view;
 9}
10
11// Usage (call in async context)
12// fetchBinary("/path/to/file.bin");
  • Response.arrayBuffer()で得たArrayBufferをそのままTypedArrayに渡して扱えます。画像や音声、カスタムバイナリプロトコルで使います。

性能上のヒントとよくある落とし穴

TypedArrayを使うときに知っておくと役立つ「性能面でのコツ」と「よくある落とし穴」として、次のような点があります。

  • 不要なコピーを避ける 大きなデータを高速に処理したい場合は、subarrayで部分的なビューを作ったり、同じArrayBufferを複数のビューで共有したりすることで、ムダなコピーを減らせます。

  • エンディアンに注意する ネットワーク通信やファイル形式では、データの並び方(バイト順)が決まっていることが多くあります。DataViewを使えば、読み書き時にエンディアンを明示できるため、意図しない解釈のズレを防げます。

  • 型ごとの値の範囲を意識する たとえばUint8は0〜255までしか扱えません。負の値を入れると切り捨てや値の巻き戻りが起こるため、必要に応じて変換ルールを規定します。

  • ガーベジコレクションへの負荷を考える 大きなArrayBufferを頻繁に作り直すと、メモリ管理の負荷が高くなります。性能が重要な場面では、できるだけ既存バッファを再利用する設計を検討できます。

まとめ

TypedArrayは、バイナリデータを高速かつ効率的に扱うための仕組みです。固定長のバイト領域を確保するArrayBufferと、その中身を特定の型で読み書きするTypedArrayDataViewを組み合わせて使うことで、柔軟なバイナリ操作ができるようになります。用途に応じてsubarray/sliceDataViewを使い分け、エンディアンやコピーへの注意を含めて設計できます。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video