JavaScript/型付き配列
型付き配列(かたつきはいれつ、Typed array)は特定のデータ型を持つ要素しか扱えないという制限があります。 つまり、整数型のみ、あるいは浮動小数点数型のみを扱うことができます。 型付き配列を使うことで、メモリの効率的な利用や、高速なデータ処理が可能になるため、JavaScriptのプログラマにとっては非常に重要な知識と言えます。
概要
編集型付き配列は、C言語の配列のように要素数を予め決め全ての要素の型が同じオブジェクトなインデックス可能なコレクションです。
- 型付き配列を使って浮動小数点数のビットパターンを表示する
const f64 = new Float64Array([0, 1, NaN, Infinity, -Infinity, Math.PI]) const b64 = new BigUint64Array(f64.buffer) f64.forEach((x, i) => console.log(`${(""+x).padStart(17," ")}: ${b64[i].toString(2).padStart(64, "0")}`) )
- 実行結果
0: 0000000000000000000000000000000000000000000000000000000000000000 1: 0011111111110000000000000000000000000000000000000000000000000000 NaN: 0111111111111000000000000000000000000000000000000000000000000000 Infinity: 0111111111110000000000000000000000000000000000000000000000000000 -Infinity: 1111111111110000000000000000000000000000000000000000000000000000 3.141592653589793: 0100000000001001001000011111101101010100010001000010110100011000
- 行ごとに説明しましょう。
- Float64Arrayオブジェクトf64を
[0, 1, NaN, Infinity, -Infinity, Math.PI]
をパラメータとして生成 - BigUint64Arrayオブジェクトb64をFloat64Arrayオブジェクトのバッファを引数に生成
- Float64Arrayの要素を順に
- BigUint64Arrayを使って二進文字列化
6種類のIEEE 754の64ビット倍精度浮動小数点数のバイナリ表現を表示したもので、TypedArray を使うと内部表現へのアクセスが buffer プロパティを使って可能になる事を示す例となっています。
この様な ArrayBuffer を扱うためには、DataViewオブジェクトが用意されていますが今回は同じ64ビット同士にすることでエンディアンの問題を回避しました。
TypedArray オブジェクト
編集TypedArray はコンストラクターとしては呼び出さず、Float64ArrayオブジェクトやBigUint64Arrayオブジェクトのような TypedArray オブジェクトのインスタンスを生成します。
// TypedArray オブジェクトの生成例 // Int8Array: 8ビット符号付き整数のTypedArrayオブジェクト const int8Array = new Int8Array(4); console.log(int8Array); // Int8Array [ 0, 0, 0, 0 ] // Uint8Array: 8ビット符号なし整数のTypedArrayオブジェクト const uint8Array = new Uint8Array([1, 2, 3, 4]); console.log(uint8Array); // Uint8Array [ 1, 2, 3, 4 ] // Uint8ClampedArray: オーバーフロー対策された8ビット符号なし整数のTypedArrayオブジェクト const uint8ClampedArray = new Uint8ClampedArray([300, -50, 1000, 0]); console.log(uint8ClampedArray); // Uint8ClampedArray [ 255, 0, 255, 0 ] // Int16Array: 16ビット符号付き整数のTypedArrayオブジェクト const int16Array = new Int16Array([32767, -32768, 0, 12345]); console.log(int16Array); // Int16Array [ 32767, -32768, 0, 12345 ] // Uint16Array: 16ビット符号なし整数のTypedArrayオブジェクト const uint16Array = new Uint16Array([65535, 0, 1234, 9876]); console.log(uint16Array); // Uint16Array [ 65535, 0, 1234, 9876 ] // Int32Array: 32ビット符号付き整数のTypedArrayオブジェクト const int32Array = new Int32Array([-2147483648, 0, 2147483647, 123456]); console.log(int32Array); // Int32Array [ -2147483648, 0, 2147483647, 123456 ] // Uint32Array: 32ビット符号なし整数のTypedArrayオブジェクト const uint32Array = new Uint32Array([4294967295, 0, 987654321, 555555]); console.log(uint32Array); // Uint32Array [ 4294967295, 0, 987654321, 555555 ] // Float32Array: 32ビット単精度浮動小数点数のTypedArrayオブジェクト const float32Array = new Float32Array([3.14, -0.5, 1.234, 7.89]); console.log(float32Array); // Float32Array [ 3.140000104904175, -0.5, 1.2340000867843628, 7.889999866485596 ] // Float64Array: 64ビット倍精度浮動小数点数のTypedArrayオブジェクト const float64Array = new Float64Array([1.2345678901234567, -9876543.210, 0, 12345.6789]); console.log(float64Array); // Float64Array [ 1.2345678901234567, -9876543.21, 0, 12345.6789 ] // BigInt64Array: 64ビット符号付き整数のTypedArrayオブジェクト const bigInt64Array = new BigInt64Array([BigInt('-9223372036854775808'), BigInt('0'), BigInt('9223372036854775807')]); console.log(bigInt64Array); // BigInt64Array [ -9223372036854775808n, 0n, 9223372036854775807n ] // BigUint64Array: 64ビット符号なし整数のTypedArrayオブジェクト const bigUint64Array = new BigUint64Array([BigInt('18446744073709551615'), BigInt('0'), BigInt('1234567890123456789')]); console.log(bigUint64Array); // BigUint64Array [ 18446744073709551615n, 0n, 1234567890123456789n ]
TypedArray オブジェクト 要素型 Int8Array 8ビット符号付き整数 Uint8Array 8ビット符号なし整数 Uint8ClampedArray オーバーフロー対策された8ビット符号なし整数 Int16Array 16ビット符号付き整数 Uint16Array 16ビット符号なし整数 Int32Array 32ビット符号付き整数 Uint32Array 32ビット符号なし整数 Float32Array 32ビット単精度浮動小数点数 Float64Array 64ビット倍精度浮動小数点数組み込み数値プリミティブと同じ精度。 BigInt64Array 64ビット符号付き整数 BigUint64Array 64ビット符号なし整数
Uint8ClampedArray
編集オーバーフロー対策と丸め処理を施された8ビット符号なし整数のTypedArray オブジェクト。
以下の変換規則に従います[1]
- 数値に変換する
- NaN, +0, -0, Infinity 及び -Infinity は+0とする
- 0未満は0に丸める
- 255を超えると255に丸める
- 最近接偶数丸めを行う
最近接偶数丸めは、以下のような特徴があります
- 丸めるべき値が整数の場合は、そのままの整数に丸められます。
- 丸めるべき値が小数で、小数部が0.5の場合は、最も近い偶数に丸められます。例えば、0.5は0に、1.5は2に丸められます。
- 小数部が0.5である場合、整数部が偶数の場合は最も近い偶数に、整数部が奇数の場合は最も近い奇数に丸められます。
Canvas#imageData へのアクセスを使った高速化の節に、CanvasのimageDate.data(Uint8ClampedArrayオブジェクト)を直接操作して高速化をはかる例がある。
const ui8c = new Uint8ClampedArray(1);
[ NaN, +0, -0, -1, 256, 0.5, 0.5000000000001, 1.5, 1.5000000000001, ].forEach(x => console.log(ui8c[0] = x, ui8c[0]));
/*
NaN 0
0 0
-0 0
-1 0
256 255
0.5 0
0.5000000000001 1
*/
メソッド
編集TypedArray オブジェクトは、通常の JavaScript 配列と同様に、さまざまなメソッドを提供しています。以下は、主な TypedArray オブジェクトのメソッドのいくつかです。
- 共通のメソッド
- length
- 配列の要素数を返します。
const int32Array = new Int32Array([1, 2, 3, 4]); console.log(int32Array.length); // 4
- subarray(begin[, end])
- 部分的な TypedArray を新しい TypedArray として返します。
const int32Array = new Int32Array([1, 2, 3, 4]); const subArray = int32Array.subarray(1, 3); console.log(subArray); // Int32Array [ 2, 3 ]
- slice(begin[, end])
- 部分的な TypedArray のコピーを新しい TypedArray として返します。
const int32Array = new Int32Array([1, 2, 3, 4]); const slicedArray = int32Array.slice(1, 3); console.log(slicedArray); // Int32Array [ 2, 3 ]
- 数値演算関連のメソッド
- reduce(callback[, initialValue])
- 配列の各要素に対してコールバック関数を適用し、単一の累積値を返します。
const float64Array = new Float64Array([1.5, 2.5, 3.5]); const sum = float64Array.reduce((acc, val) => acc + val, 0); console.log(sum); // 7.5
- map(callback)
配列の各要素に対してコールバック関数を適用し、新しい TypedArray を返します。
const int8Array = new Int8Array([1, 2, 3]); const squaredArray = int8Array.map(val => val * val); console.log(squaredArray); // Int8Array [ 1, 4, 9 ]
- filter(callback)
- 配列の各要素に対してフィルタリングを行い、新しい TypedArray を返します。
const int16Array = new Int16Array([10, -5, 7, -3]); const positiveNumbers = int16Array.filter(val => val > 0); console.log(positiveNumbers); // Int16Array [ 10, 7 ]
ArrayとTypedArrayのメソッドの差異
編集ArrayとTypedArrayにはいくつかのメソッドが共通して存在しますが、いくつかの違いもあります。以下に、ArrayにあってTypedArrayにないメソッド、あるいはその逆をいくつか挙げます。
ArrayにあってTypedArrayにないメソッド
編集- concat()
- TypedArrayには concat() メソッドが存在しません。代わりに TypedArray では、スプレッド構文を使用して結合します。
// Arrayの例 const array1 = [1, 2, 3]; const array2 = [4, 5, 6]; const newArray = array1.concat(array2); console.log(newArray); // [1, 2, 3, 4, 5, 6] // TypedArrayの例 const typedArray1 = new Int32Array([1, 2, 3]); const typedArray2 = new Int32Array([4, 5, 6]); const newTypedArray = new Int32Array([...typedArray1, ...typedArray2]); console.log(newTypedArray); // Int32Array [1, 2, 3, 4, 5, 6]
- reverse()
- TypedArrayには reverse() メソッドが存在しません。代わりに、スプレッド構文や独自の方法を使用して逆順にします。
// Arrayの例 const array = [1, 2, 3]; const reversedArray = array.reverse(); console.log(reversedArray); // [3, 2, 1] // TypedArrayの例 const typedArray = new Int32Array([1, 2, 3]); const reversedTypedArray = new Int32Array([...typedArray].reverse()); console.log(reversedTypedArray); // Int32Array [3, 2, 1]
TypedArrayにあってArrayにないメソッド
編集- set()
- TypedArrayは set() メソッドを提供しており、一方のTypedArrayから別のTypedArrayにデータをコピーできます。
const sourceTypedArray = new Int32Array([1, 2, 3]); const targetTypedArray = new Int32Array(3); // TypedArrayからTypedArrayにデータをコピー targetTypedArray.set(sourceTypedArray); console.log(targetTypedArray); // Int32Array [1, 2, 3]
- subarray(begin[, end])
- TypedArrayは subarray() メソッドを提供しており、一部のTypedArrayを新しいTypedArrayとして取り出すことができます。
const typedArray = new Int32Array([1, 2, 3, 4, 5]); // TypedArrayから一部を取り出す const subArray = typedArray.subarray(1, 4); console.log(subArray); // Int32Array [2, 3, 4]
このように、ArrayとTypedArrayは基本的な操作においては共通のメソッドを持っていますが、細かな違いがあります。注意して使用することが重要です。
スプレッド構文
編集TypedArray オブジェクトでもスプレッド構文を使用することができます。スプレッド構文は、配列や TypedArray オブジェクトから要素を取り出して新しい配列や TypedArray を作成するための便利な構文です。
以下は、スプレッド構文を使用した TypedArray の例です:
// TypedArray オブジェクトの作成 const sourceArray = new Int32Array([1, 2, 3, 4]); // スプレッド構文を使用して新しい TypedArray を作成 const newArray = new Int32Array([...sourceArray, 5, 6]); console.log(newArray); // Int32Array [ 1, 2, 3, 4, 5, 6 ]
この例では、既存の sourceArray
から要素を取り出し、新しい Int32Array
オブジェクト newArray
を作成しています。このようにして、スプレッド構文を用いて TypedArray の要素を効果的に結合できます。ただし、スプレッド構文を使用して TypedArray のコピーを作成する場合、パフォーマンスに注意する必要があります。
TypedArrayのユースケース
編集TypedArrayはバイナリデータを効率的に扱うために設計されたJavaScriptの機能であり、主に以下のような場面で活用されます。
- 画像処理とキャンバス操作: TypedArrayはピクセルデータを効率的に操作するために使用されます。
Uint8ClampedArray
は特に画像処理でよく使用され、Canvas APIのgetImageData
などで取得されたピクセルデータがこの型のTypedArrayとして提供されます。const canvas = document.getElementById('myCanvas'); const context = canvas.getContext('2d'); const imageData = context.getImageData(0, 0, canvas.width, canvas.height); const pixelData = new Uint8ClampedArray(imageData.data.buffer); // pixelDataを操作して画像処理を行う
- 音声処理: 音声データもバイナリデータで表現されます。TypedArrayは音声信号の処理に使用され、
Float32Array
やInt16Array
がよく利用されます。const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const buffer = audioContext.createBuffer(1, 44100, 44100); // 1チャンネル, サンプリングレート44100 const audioData = new Float32Array(buffer.getChannelData(0)); // audioDataを操作して音声処理を行う
- ネットワーク通信: ネットワーク通信時には、バイナリデータを受け取り、TypedArrayを使用してデータの解析や処理を行います。WebSocketやFetch APIで受信したデータをTypedArrayに変換して扱うことがあります。
fetch('https://example.com/data.bin') .then(response => response.arrayBuffer()) .then(data => { const dataArray = new Uint8Array(data); // dataArrayを解析してデータ処理を行う });
- 3Dグラフィックス: WebGLやWebGPUなどの3DグラフィックスAPIでは、頂点データやテクスチャデータをTypedArrayとして扱います。これにより、高効率にバイナリデータを操作できます。
const vertexData = new Float32Array([/* ... */]); const indexData = new Uint16Array([/* ... */]); // WebGLでvertexDataやindexDataを扱う
- 大規模なデータ処理: 大規模なデータセットやバイナリデータの処理において、TypedArrayはメモリ効率や演算速度の向上を提供します。これにより、数値計算が必要なアプリケーションやライブラリでパフォーマンスが向上します。
これらはTypedArrayが有用である一部のシーンであり、バイナリデータを効率的に取り扱うために欠かせない機能です。
TypedArrayのベストプラクティス
編集TypedArrayを使用する際には、いくつかのベストプラクティスに従うことが重要です。以下は、TypedArrayの効果的な使用に関する一般的なベストプラクティスです。
- 型付き配列の正確なサイズを把握する: TypedArrayは固定サイズのバッファを持ちます。事前にデータサイズを正確に知っておくことが重要です。サイズを過小評価するとデータが失われる可能性があり、過大評価すると余分なメモリを使用することになります。
- バッファの再利用: TypedArrayのバッファを再利用することで、メモリの効率が向上します。余分なバッファの割り当てを避け、不要なガベージコレクションを回避できます。
// バッファを再利用する例 const buffer = new ArrayBuffer(8); const int32Array = new Int32Array(buffer); // 以後、同じバッファを再利用して異なるTypedArrayを作成 const float32Array = new Float32Array(buffer);
- ビューの使用:
DataView
を使用してTypedArrayのバッファを異なるビューで表示することができます。これは特にバイトオーダーや異なる型のデータにアクセスする際に便利です。const int32Array = new Int32Array([1, 2, 3, 4]); const dataView = new DataView(int32Array.buffer); const firstValue = dataView.getInt32(0, true); // リトルエンディアン
- バイナリデータの処理: TypedArrayは主にバイナリデータを扱うためにデザインされています。バイナリデータの読み書き、変換、処理にはTypedArrayのメソッドやビューを活用しましょう。
const byteArray = new Uint8Array([1, 2, 3, 4, 5]); const uint32Array = new Uint32Array(byteArray.buffer); console.log(uint32Array); // Uint32Array [ 67305985 ]
- パフォーマンスの最適化: TypedArrayは高速な数値演算を提供しますが、無駄なメモリのコピーを避けるために注意が必要です。スプレッド構文や
Array.from()
を使うと、性能が低下する可能性があります。// パフォーマンスの観点からは避けるべき例 const typedArray = new Float32Array([1, 2, 3]); const newArray = new Float32Array([...typedArray]); // 非効率的なコピー
- 上記の代わりに、
subarray()
や直接TypedArrayのコンストラクタを使用すると効率的です。 // 直接TypedArrayのコンストラクタを使用し効率に配慮した例。 const typedArray = new Float32Array([1, 2, 3]); const newArray = new Float32Array(typedArray.buffer, typedArray.byteOffset, typedArray.length);
これらのベストプラクティスを踏まえてTypedArrayを使うことで、メモリの効率を向上させ、高速なバイナリデータ処理を実現できます。