ウェブ開発では、ファイルの処理(作成、アップロード、ダウンロード)を行う際に、主にバイナリデータに出会います。もう一つの典型的なユースケースは画像処理です。
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…)を使用してください。