ウェブ開発では、ファイルの処理(作成、アップロード、ダウンロード)を行う際に、主にバイナリデータに出会います。もう一つの典型的なユースケースは画像処理です。
JavaScriptではこれらすべてが可能であり、バイナリ演算は高性能です。
ただし、多くのクラスがあるため、やや混乱が生じます。いくつか例を挙げると
ArrayBuffer、Uint8Array、DataView、Blob、Fileなど。
JavaScriptにおけるバイナリデータは、他の言語と比較して非標準的な方法で実装されています。しかし、整理整頓すれば、すべてがかなりシンプルになります。
基本的なバイナリオブジェクトはArrayBufferです。これは、固定長の連続したメモリ領域への参照です。
次のように作成します。
let buffer = new ArrayBuffer(16); // create a buffer of length 16
alert(buffer.byteLength); // 16
これは16バイトの連続したメモリ領域を割り当て、ゼロで事前に埋め込みます。
ArrayBufferは何かの配列ではありません。考えられる混乱の発生源を取り除きましょう。ArrayBufferはArrayと共通点がありません。
- 固定長であり、増減できません。
- メモリにはちょうどそのだけのスペースを取ります。
- 個々のバイトにアクセスするには、
buffer[index]ではなく、別の「ビュー」オブジェクトが必要です。
ArrayBufferはメモリ領域です。そこに何が保存されているのでしょうか?それはわかりません。生のバイトシーケンスだけです。
ArrayBufferを操作するには、「ビュー」オブジェクトを使用する必要があります。
ビューオブジェクト自体は何も保存しません。これは、「眼鏡」であり、ArrayBufferに保存されているバイトの解釈を提供します。
例えば
Uint8Array–ArrayBuffer内の各バイトを、0から255までの値を持つ個別の数値として扱います(バイトは8ビットなので、それだけしか保持できません)。このような値は「8ビット符号なし整数」と呼ばれます。Uint16Array– すべての2バイトを整数として扱い、0から65535までの値を持ちます。「16ビット符号なし整数」と呼ばれます。Uint32Array– すべての4バイトを整数として扱い、0から4294967295までの値を持ちます。「32ビット符号なし整数」と呼ばれます。Float64Array– すべての8バイトを浮動小数点数として扱い、5.0x10-324から1.8x10308までの値を持ちます。
したがって、16バイトのArrayBuffer内のバイナリデータは、16個の「小さな数」、または8個のより大きな数(2バイトずつ)、または4個のさらに大きな数(4バイトずつ)、または2個の高精度浮動小数点値(8バイトずつ)として解釈できます。
ArrayBufferは中心となるオブジェクトであり、すべてのもとの、生のバイナリデータです。
しかし、それに書き込んだり、それを反復処理したり、基本的にほとんどの操作を行う場合は、ビュー(例:
let buffer = new ArrayBuffer(16); // create a buffer of length 16
let view = new Uint32Array(buffer); // treat buffer as a sequence of 32-bit integers
alert(Uint32Array.BYTES_PER_ELEMENT); // 4 bytes per integer
alert(view.length); // 4, it stores that many integers
alert(view.byteLength); // 16, the size in bytes
// let's write a value
view[0] = 123456;
// iterate over values
for(let num of view) {
alert(num); // 123456, then 0, 0, 0 (4 values total)
}
TypedArray
これらのビュー(Uint8Array、Uint32Arrayなど)の一般的な用語はTypedArrayです。これらは同じメソッドとプロパティのセットを共有しています。
TypedArrayという名前のコンストラクタはありません。これは単に、ArrayBufferに対するビューの1つを表す一般的な「包括的」な用語です。Int8Array、Uint8Arrayなど、完全なリストはすぐに続きます。
new TypedArrayのようなものを見ると、new Int8Array、new Uint8Arrayなどのいずれかのことを意味します。
型付き配列は通常の配列のように動作します。インデックスを持ち、反復可能です。
型付き配列コンストラクタ(Int8ArrayまたはFloat64Arrayのどちらでも構いません)は、引数の型によって異なる動作をします。
引数には5つのバリエーションがあります。
new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();
-
ArrayBuffer引数が提供された場合、その上にビューが作成されます。この構文は既に使用しています。オプションで、開始位置(デフォルトは0)の
byteOffsetと長さ(デフォルトではバッファの最後まで)を指定できます。その場合、ビューはbufferの一部のみをカバーします。 -
Arrayまたは配列のようなオブジェクトが与えられた場合、同じ長さの型付き配列を作成し、内容をコピーします。これを使用して、データで配列を事前に埋め込むことができます。
let arr = new Uint8Array([0, 1, 2, 3]); alert( arr.length ); // 4, created binary array of the same length alert( arr[1] ); // 1, filled with 4 bytes (unsigned 8-bit integers) with given values -
別の
TypedArrayが提供された場合、同じことを行います。同じ長さの型付き配列を作成し、値をコピーします。必要に応じて、その過程で値が新しい型に変換されます。let arr16 = new Uint16Array([1, 1000]); let arr8 = new Uint8Array(arr16); alert( arr8[0] ); // 1 alert( arr8[1] ); // 232, tried to copy 1000, but can't fit 1000 into 8 bits (explanations below) -
数値引数
lengthの場合、その多くの要素を含む型付き配列を作成します。そのバイト長は、単一アイテムTypedArray.BYTES_PER_ELEMENTのバイト数にlengthを掛けたものになります。let arr = new Uint16Array(4); // create typed array for 4 integers alert( Uint16Array.BYTES_PER_ELEMENT ); // 2 bytes per integer alert( arr.byteLength ); // 8 (size in bytes) -
引数がない場合、長さ0の型付き配列を作成します。
ArrayBufferに言及せずに、TypedArrayを直接作成できます。しかし、ビューは基となるArrayBufferがなければ存在できないため、最初のもの(提供された場合)を除くすべての場合で自動的に作成されます。
基となるArrayBufferにアクセスするには、TypedArrayに次のプロパティがあります。
buffer–ArrayBufferを参照します。byteLength–ArrayBufferの長さ。
したがって、常に1つのビューから別のビューに移動できます。
let arr8 = new Uint8Array([0, 1, 2, 3]);
// another view on the same data
let arr16 = new Uint16Array(arr8.buffer);
型付き配列のリストを以下に示します。
Uint8Array、Uint16Array、Uint32Array– 8、16、32ビットの整数の数。Uint8ClampedArray– 8ビット整数の場合、代入時に「クランプ」します(下記参照)。
Int8Array、Int16Array、Int32Array– 符号付き整数(負になる可能性があります)。Float32Array、Float64Array– 32ビットと64ビットの符号付き浮動小数点数。
int8または同様の単一値型はありません。Int8Arrayのような名前にもかかわらず、JavaScriptにはintやint8のような単一値型はありません。
これは論理的です。Int8Arrayはこれらの個々の値の配列ではなく、ArrayBufferのビューだからです。
範囲外の動作
型付き配列に範囲外の値を書こうとするとどうなるでしょうか?エラーは発生しません。しかし、余分なビットは切り捨てられます。
例えば、256をUint8Arrayに入れようとします。2進数形式で、256は100000000(9ビット)ですが、Uint8Arrayは値ごとに8ビットしか提供しないため、利用可能な範囲は0から255になります。
より大きな数値の場合、最右端(最下位)の8ビットのみが保存され、残りは切り捨てられます。
したがって、ゼロになります。
257の場合、2進数形式は100000001(9ビット)であり、最右端の8ビットが保存されるため、配列には1が入ります。
言い換えれば、28を法とする数が保存されます。
デモはこちら
let uint8array = new Uint8Array(16);
let num = 256;
alert(num.toString(2)); // 100000000 (binary representation)
uint8array[0] = 256;
uint8array[1] = 257;
alert(uint8array[0]); // 0
alert(uint8array[1]); // 1
Uint8ClampedArrayはこの点で特殊であり、動作が異なります。255より大きい数値には255を、負の数には0を保存します。この動作は画像処理に役立ちます。
TypedArrayメソッド
TypedArrayには、注目すべき例外を除いて、通常のArrayメソッドがあります。
反復、map、slice、find、reduceなどを実行できます。
ただし、できないことがいくつかあります。
spliceはありません – 型付き配列はバッファのビューであり、それらは固定された連続したメモリ領域であるため、値を「削除」することはできません。できることはゼロを代入することだけです。concatメソッドはありません。
2つの追加メソッドがあります。
arr.set(fromArr, [offset])は、fromArrのすべての要素をarrにコピーし、位置offset(デフォルトは0)から開始します。arr.subarray([begin, end])は、beginからend(排他的)まで、同じ型の新しいビューを作成します。これはsliceメソッド(これもサポートされています)に似ていますが、何もコピーしません。データの指定された部分に対して操作を行う新しいビューを作成するだけです。
これらのメソッドを使用すると、型付き配列をコピーしたり、混合したり、既存の配列から新しい配列を作成したりすることができます。
DataView
DataViewは、ArrayBufferに対する特別な超柔軟な「型なし」ビューです。これにより、任意のフォーマットで任意のオフセットのデータにアクセスできます。
- 型付き配列の場合、コンストラクタによってフォーマットが決まります。配列全体は均一である必要があります。i番目の数は
arr[i]です。 DataViewでは、.getUint8(i)または.getUint16(i)などのメソッドを使用してデータにアクセスします。構築時ではなく、メソッド呼び出し時にフォーマットを選択します。
構文
new DataView(buffer, [byteOffset], [byteLength])
buffer– 基となるArrayBuffer。型付き配列とは異なり、DataViewは独自にバッファを作成しません。準備しておく必要があります。byteOffset– ビューの開始バイト位置(デフォルトは0)。byteLength– ビューのバイト長(デフォルトはbufferの最後まで)。
例えば、ここでは同じバッファから異なるフォーマットの数値を抽出します。
// binary array of 4 bytes, all have the maximal value 255
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// get 8-bit number at offset 0
alert( dataView.getUint8(0) ); // 255
// now get 16-bit number at offset 0, it consists of 2 bytes, together interpreted as 65535
alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)
// get 32-bit number at offset 0
alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
dataView.setUint32(0, 0); // set 4-byte number to zero, thus setting all bytes to 0
DataViewは、同じバッファに混合フォーマットのデータを保存する場合に最適です。例えば、(16ビット整数、32ビット浮動小数点数)のペアのシーケンスを保存する場合、DataViewを使用すると簡単にアクセスできます。
概要
ArrayBufferは中心となるオブジェクトであり、固定長の連続したメモリ領域への参照です。
ArrayBufferに対してほとんどの操作を行うには、ビューが必要です。
- それは
TypedArrayである可能性があります。Uint8Array、Uint16Array、Uint32Array– 8、16、32ビットの符号なし整数。Uint8ClampedArray– 8ビット整数の場合、代入時に「クランプ」します。Int8Array、Int16Array、Int32Array– 符号付き整数(負になる可能性があります)。Float32Array、Float64Array– 32ビットと64ビットの符号付き浮動小数点数。
- または
DataView– フォーマットを指定するためにメソッドを使用するビュー(例:getUint8(offset))。
ほとんどの場合、ArrayBufferを「共通の分母」として隠したまま、型付き配列を直接作成して操作します。「共通の分母」として隠しておきます。必要に応じて、.bufferとしてアクセスし、別のビューを作成できます。
バイナリデータに対して動作するメソッドの説明で使用される追加の用語が2つあります。
ArrayBufferViewは、これらのすべての種類のビューの包括的な用語です。BufferSourceは、ArrayBufferまたはArrayBufferViewの包括的な用語です。
これらの用語は次の章で説明します。BufferSourceは最も一般的な用語の1つであり、「あらゆる種類のバイナリデータ」つまりArrayBufferまたはそのビューを意味します。
チートシートはこちら
コメント
<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合は、サンドボックス(plnkr、jsbin、codepen…)を使用してください。