JavaScriptの型付き配列
2021/03/01更新
目次
基本
JavaScriptの型付き配列とは
JavaScriptにおいて、通常の配列は、型の制限無く自由な値を格納することができ、また要素数の拡張なども自由にできる。その反面、巨大なバイナリデータなどを扱おうとした場合に、処理速度の面で劣ってしまう。
そこで、より高速でデータにアクセスできるようにすることを目的に、格納できる値の型や機能に制限を設けたものが型付き配列である。通常の配列と似ている部分もあるが、似て非なるものであり、Array.isArray()
による判定はfalse
となる。
型付き配列は、ECMAScript 2015(ES6)で登場した。
バッファとビュー
型付き配列は、内部的には、ArrayBuffer
によるメモリの確保(バッファ)と、TypedArray
による値へのアクセス(ビュー)という2つの実装によって成り立っている。ArrayBuffer
を直接触ることは少なく、通常はビューであるTypedArray
のサブクラスを通して型付き配列を操作する。TypedArray
のサブクラスには以下のようなものがある。
クラス | 説明 | 値の範囲 |
---|---|---|
| 1バイト符号付き整数 | -128~127 |
| 1バイト整数 | 0~255 |
| 1バイト整数(切り詰め) | 0~255 |
| 2バイト符号付き整数 | -32767~32768 |
| 2バイト整数 | 0~65535 |
| 4バイト符号付き整数 | -2147483648~2147483647 |
| 4バイト整数 | 0~4294967295 |
| 単精度浮動小数点数(4バイト) | -3.403×10の38乗~-3.403×10の38乗 |
| 倍精度浮動小数点数(8バイト) | -1.798×10の308乗~1.798×10の308乗 |
確保されたバッファに対して、複数のビューを持つことができるので、例えば16バイトのバッファに対して、Uint8Array
をビューとすれば1バイト×16要素の型付き配列となり、Uint16Array
をビューとすれば2バイト×8要素の型付き配列となる。このとき、2バイト以上のビューを使用する場合は、エンディアン(バイト列の配置順序)に注意する必要がある(後述)。
Uint8ClampedArray
は、基本的にUint8Array
と同じであるが、範囲外の値を入れられたときの挙動が異なる。Uint8Array
では下位ビットのみが採用されるが、Uint8ClampedArray
では、0以下の値は全て0、255以上の値は全て255と見なされる。
ArrayBuffer
とTypedArray
は、対応していれば必ず両方実装されているため、型付き配列を使用可能かどうかは、window.ArrayBuffer
が存在するかどうかだけを確認すればよい。主要なブラウザの中では、IE9以前が非対応となっている。
「Uint」の「U」は「Unsigned(符号無し)」の「U」である。
型付き配列
型付き配列の作成
型付き配列を新しく作成するには、以下のようにする。型付き配列を作成すると、内部的にArrayBuffer
も自動的に作成されるため、バッファを別途作成する必要はない。また、型付き配列やバッファは、一度作成するとサイズ(要素数)を変更することはできない。
// 要素数5(2バイト×5=10バイト)のUint16Arrayを作成する。 const arr1 = new Uint16Array(5); console.log(arr1); // [0, 0, 0, 0, 0] ※初期値は0 console.log(arr1[5]); // 範囲外は「undefined」 // 通常の配列で初期データを指定して作成する。 const arr2 = new Uint16Array([254, 255, 256, 257, 258]); console.log(arr2); // [254, 255, 256, 257, 258] // 他の型付き配列で初期データを指定して作成する。 // 型に収まらない数値が与えられた場合は、下位ビットのみが採用される。 const arr3 = new Uint8Array(arr2); console.log(arr3); // [254, 255, 0, 1, 2]
なお、バッファだけを作成することもできるが、これだけでは型付き配列とはならない。
// 10バイトのバッファを作成する。 const buf1 = new ArrayBuffer(10); console.log(buf1.byteLength); // 10 // バッファの一部をコピーする。 const buf2 = buf1.slice(5, 10);
型付き配列のサイズ
型付き配列の要素数は、通常の配列と同じくlength
で分かる。また、バイト数はbyteLength
で分かる。
// Uint8Arrayは1バイトの型付き配列 const arr1 = new Uint8Array(10); console.log(arr1.length); // 10 console.log(arr1.byteLength); // 10 // Float64Arrayは8バイトの型付き配列 const arr2 = new Float64Array(10); console.log(arr2.length); // 10 console.log(arr2.byteLength); // 80
1要素あたり何バイトであるかは、BYTES_PER_ELEMENT
で確認できる。
const arr = new Float64Array(10); console.log(arr.BYTES_PER_ELEMENT); // 8 console.log(Float64Array.BYTES_PER_ELEMENT); // 8
既存のバッファからの作成
同じバッファに対して、別の型付き配列としてアクセスすることが簡単にできる。以下の表は、10バイトのバッファに対して、いろいろな位置からいろいろな要素数で型付き配列を作成する例である。
バッファの値「0x04
」「0x01
」に対してUint16Array
での値が260となるのは、260は2バイト整数として考えると0x0104
だからである。つまり、260=4×256の0乗+1×256の1乗ということである。このようにバッファ上で先に配置されているバイトを下位バイトと見なす方式をリトルエンディアンという。環境によっては、先にあるバイトが上位バイトと見なされ0x0401
である1025(=4×256の1乗+1×256の0乗)となることもある。この場合はビッグエンディアンという。マルチバイトの型を扱うときにどちらのエンディアンになるかは環境次第となるため注意が必要である。また、扱うデータによっても仕様でエンディアンが決められている場合がある。
以下の表の「1バイト目から3要素のUint16Array
」は作成できない。Uint16Array
は2バイト整数であるため、必ずバッファの2の倍数の位置からしか作成することができないためである。同様に、例えばUint32Array
であれば、4の倍数の位置からしか作成できない。
バッファ位置(バイト) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
バッファ内の値 |
|
|
|
|
|
|
|
|
|
|
0バイト目から10要素の | 4 | 1 | 5 | 1 | 6 | 1 | 7 | 1 | 8 | 1 |
0バイト目から5要素の | 260 | 261 | 262 | 263 | 264 | |||||
1バイト目から6要素の | 1 | 5 | 1 | 6 | 1 | 7 | ||||
1バイト目から3要素の | 作成不可 | |||||||||
2バイト目から6要素の | 5 | 1 | 6 | 1 | 7 | 1 | ||||
2バイト目から3要素の | 261 | 262 | 263 |
// 要素数5(2バイト×5=10バイト)のUint16Arrayを作成する。 const arr1 = new Uint16Array([260, 261, 262, 263, 264]); console.log(arr1); // [260, 261, 262, 263, 264] // arr1の1要素目から3要素(=4-1)を使って別のUint16Arrayを作成する。 const arr2 = arr1.subarray(1, 4); console.log(arr2); // [261, 262, 263] // arr1のバッファの2バイト目から別のUint16Arrayを3要素分(2バイト×3=6バイト)作成する。 const arr3 = new Uint16Array(arr1.buffer, 2, 3); console.log(arr3); // [261, 262, 263] // arr1のバッファの1バイト目から別のUint8Arrayを6要素分(1バイト×6=6バイト)作成する。 const arr4 = new Uint8Array(arr1.buffer, 1, 6); console.log(arr4); // [1, 5, 1, 6, 1, 7] // arr1、arr2、arr3、arr4は、全てバッファを共有している。 arr3[0] = 0; console.log(arr1); // [260, 0, 262, 263, 264] console.log(arr2); // [0, 262, 263] console.log(arr3); // [0, 262, 263] console.log(arr4); // [1, 0, 0, 6, 1, 7] console.log(arr1.buffer == arr2.buffer); // true console.log(arr1.buffer == arr3.buffer); // true console.log(arr1.buffer == arr4.buffer); // true // バッファのどの部分を参照しているかは、byteOffsetで分かる。 console.log(arr1.byteOffset); // 0 console.log(arr2.byteOffset); // 2 console.log(arr3.byteOffset); // 2 console.log(arr4.byteOffset); // 1
上記arr2
の例で使用しているsubarray()
の第2引数は、省略すると最後の要素までとなる。
上記arr3
とarr4
の例のように、TypedArray
のコンストラクタにバッファを与える場合、第3引数を省略するとバッファの最後までを使用して要素の作成を試みる。ただし、得られたバイト数がその型のバイトサイズの倍数に一致しない場合はエラーとなるので注意が必要である。
型付き配列の値の更新
型付き配列の値を更新するには、通常の配列と同じような代入文で1要素ずつ更新する方法の他にも、set()
を使って広範囲の値をまとめて更新することができる。
const arr1 = new Uint16Array([111, 222, 333, 444, 555]); // 先頭から[0, 0, 0]をセット arr1.set([0, 0, 0]); console.log(arr1); // [0, 0, 0, 444, 555]; // 2要素目から[777, 888, 999]をセット arr1.set([777, 888, 999], 2); console.log(arr1); // [0, 0, 777, 888, 999]); // arr1が持つ範囲を超えてしまうので、以下はエラーとなる。 arr1.set([0, 0, 0, 0, 0, 0]); arr1.set([0, 0, 0], 3);
通常の配列との関係
型付き配列は、通常の配列とは似て非なるものである。型付き配列を通常の配列に変換するには、Array.from()
を使用すればよい。通常の配列に変換すると、型付き配列が持つアクセスの高速さは失われる。
const typedArr = new Uint16Array([111, 222, 333, 444, 555]); const normalArr = Array.from(typedArr);
また、通常の配列が持つメソッドの大半は型付き配列でも使用できる。ただし、concat()
、pop()
、push()
、shift()
、splice()
、unshift()
は型付き配列では使用できない。
DataView
DataViewとは
DataView
とは、バッファにアクセスするためのもう一つのビューである。TypedArray
は高速である反面、エンディアンの違いやバイト境界を考慮する必要があるなど低機能であったが、DataView
では速度をある程度犠牲にする代わりに、エンディアンを自由に設定できたり、任意のバイト位置から値を設定できたりする機能を持っている。
DataViewの作成
DataView
を作成するには、ArrayBuffer
が必要である。
const buf = new ArrayBuffer(10); const dataView1 = new DataView(buf); const dataView2 = new DataView(buf, 0, 5); // 0バイト目から5バイト
DataViewによる値の入出力
DataView
では、配列のような記法は使用できず、必ず以下のメソッドを使用して値を入出力する必要がある。
メソッド | 意味 |
---|---|
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
| バッファの |
各メソッドで指定するバイト位置n
は、その型のバイトサイズの倍数である必要はない。これはTypedArray
と異なる点である。ただし、範囲外の位置や、アクセスすると範囲を超えてしまうような位置は指定できない。
上記各メソッドの引数e
に真偽値を渡すことで、エンディアンを自由に指定できる。これもTypedArray
と異なる点である。true
を渡すと、リトルエンディアンで入出力され、false
を渡す、もしくは省略するとビッグエンディアンとなる。なお、1バイトの型はエンディアンを考慮する必要がないため、この引数は無い。