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自体には読み書き機能がなく、TypedArrayやDataViewでアクセスします。
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ビット |
基本的なTypedArray(Uint8Array, Int16Array, Float32Array)
TypedArrayはArrayBuffer上に「型付きのビュー」を作るものです。以下はいくつかの代表的な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 の違い
TypedArrayのsubarrayは元のバッファを共有するビューを返し、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でバイナリ取得)
ネットワークリクエストで得たArrayBufferをTypedArrayで扱う典型例です。
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と、その中身を特定の型で読み書きするTypedArrayやDataViewを組み合わせて使うことで、柔軟なバイナリ操作ができるようになります。用途に応じてsubarray/sliceやDataViewを使い分け、エンディアンやコピーへの注意を含めて設計できます。
YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。