ArrayBuffer でバイナリデータを扱う
JavaScript でも低レベルのデータを扱う機会が増えているので、バイナリデータを上手に扱う必要性が増しています。
ここでは JavaScript でバイナリデータを扱う際に活躍する ArrayBuffer の使い方とそれに関連した各種配列オブジェクトの使い方等についてみてみましょう。
ArrayBuffer は ES6 で追加されました。2019年12月現在 Chrome 68以降および Opera 64 以降でのみ利用可能です。使用する場合には利用環境に注意してください。
JavaScript でバイナリデータを扱う方法
JavaScript でバイナリデータを扱うときには ArrayBuffer を使うのが便利です。 ArrayBuffer は固定長のバイナリデータバッファを表します。
File API の FileReader などはデータを読み取った結果を ArrayBuffer として返します。そのため 「バイナリデータを使う時は ArrayBuffer を活用する」と覚えておくと、わかりやすいです。
他の言語で見かけるような byte[] というような、バイト列が欲しい場合に、 JavaScript では ArrayBuffer を使うというわけです。
ArrayBuffer はバイト列的だ、といっても、ArrayBuffer ではバイト列へのインデックスアクセスは許可されていません。 つまりバッファのインデックス 1 のバイトに 12 と書き込む、といった操作はできません。
その代わり型付きの配列オブジェクトや DataView を使って、その中のバイナリデータにアクセスしないといけません。
ちなみに次のように、無理やり次のように配列のインデックスアクセスみたいなことをしたら、 どうなるでしょうか。
var buffer = new ArrayBuffer(10);
buffer[5] = 987;
console.log(buffer);
コンソールへの出力は次の通りです。
バイト列は初期値の 0 のままです。バイト列には何も書き込まれていません。
その代わり、5 というプロパティが作成されて、それに 987 という値がセットされています。
ブラケット [] は JavaScript では、配列オブジェクトにおけるインデックスを表します。 その他に、オブジェクトでプロパティを記述するためにも使います。
この場合、ArrayBuffer のバイト列には直接インデックスアクセスができないので、プロパティとして扱われたというわけです。
ArrayBuffer を使ってバイナリデータを読み書きするには?
ArrayBuffer のバイト列に値を書き込んだり、読み取るにはどうしたらよいでしょうか。
ArrayBuffer の作成
まずは、 ArrayBuffer を作成します。
ArrayBuffer を作成するためには、バッファのサイズをバイトで指定します。 次の例では 10 バイトのサイズの ArrayBuffer を作成しています。
var buffer = new ArrayBuffer(10);
この 10 バイトのバッファでデータを読み書きします。
このバッファは単純なバイト列です。 1 バイト (8ビット) のデータを 10 個として扱うことも、 2 バイト (16ビット) のデータ 5 個として扱うこともできます。
型付き配列オブジェクトを使ってバイト列にインデックスアクセスする
1 バイトデータとして扱うならこの ArrayBuffer から Int8Array オブジェクト、 または Uint8Array オブジェクトを作成します。
Int8Array は符号付き 8 ビット整数の配列、Uint8Array は符号なし 8 ビット整数の配列です。
まずは符号なしの Uint8Array からみてみましょう。
var buffer = new ArrayBuffer(10);
var a1 = new Uint8Array(buffer);
a1[0] = 3;
a1[2] = 254;
console.log(a1);
console.log(buffer);
この実行結果は次のようになります。
Uint8Array のインデックス 0 と 2 の要素が書き換わっているのがわかりますね。
それと、ArrayBuffer の内容をダンプしてもわかるように、ちゃんと ArrayBuffer のバイト列が書き換わっています。 つまり、ここで作成した Uint8Array オブジェクトはもとの ArrayBuffer と別個のバッファを持っているのではありません。
ArrayBuffer が保持するバッファへのポインタになっているのです。
バッファへアクセスするために用いる型付き配列オブジェクトの型によって、同じバッファに対して違う解釈ができる
試しにもうひとつ、同じ ArrayBuffer をポイントする型付き配列オブジェクトを作ってみましょう。今度は符号付き整数配列の Int8Array オブジェクトを作ります。
var buffer = new ArrayBuffer(10);
var a1 = new Uint8Array(buffer);
var a2 = new Int8Array(buffer);
a1[0] = 3;
a1[2] = 254;
console.log(a1);
console.log(a2);
console.log(buffer);
符号なしの配列オブジェクト a1 に値をセットしていますが、確かに符号付きの配列オブジェクト a2 の方も書き換わっています。 確かに同じバッファを指しています。
ただ、254 をセットしたところでは符号付きの 8 ビット整数なので、-2 として出力されています。
その他、16 ビット (2 バイト) で一つの整数とするための Uint16Array や Int16Array もあります。
試しに、次のように Uint8Array を使って 3バイト目 (インデックス 2) に 1 を書き込み、4 バイト目 (インデックス 3) に 2 を書き込むと、 その箇所は Int16Array でみると 513 となります。
var buffer = new ArrayBuffer(10);
var a1 = new Uint8Array(buffer);
var a2 = new Int16Array(buffer);
a1[0] = 1;
a1[2] = 1;
a1[3] = 2;
console.log(a1);
console.log(a2);
console.log(buffer);
なぜ 513 になるかというと、a1[2] = 1; と a1[3] = 2; の箇所は上位バイトが 2 で下位バイトが 1 としたことに相当するからです。つまり、ビット(2進数)で言うと 00000010 00000001 となったのと同じです。
従って 10進数にすると \(2^9 + 2^0 = 512 + 1 = 513\) です。
その他の型付き配列
その他にも型付き配列オブジェクトは次のようにいくつかの種類があります。
- Array
- Int8Array
- Uint8Array
- Uint8ClampedArray
- Int16Array
- Uint16Array
- Int32Array
- Uint32Array
- Float32Array
- Float64Array
- BigInt64Array
- BigUint64Array
だいたい名前から役割が想像できると思います。
ひとつだけ特別なのがありますね。Uinit8ClampedArray です。
Uint8ClampedArray とは?
Uint8ClampedArray の説明をする前に、普通の8ビットの符号なし整数配列 Uint8Array の動作を見ておきましょう。
8ビットの符号なし整数で表される範囲は [0, 255] の範囲ですが、もし負の値や、小数点付きの値や、255 より大きい値をセットしたらどうなるかみてみましょう。
var a = new Uint8Array(4);
a[0] = -10;
a[1] = 1.5;
a[2] = 300;
console.log(a);
この結果、次のようになります。
簡単に言えばデータがデタラメに壊れています。Uint8ClampedArray ではこうした問題を解決 (緩和) することを目的としています。
Uint8ClampedArray は型付き配列オブジェクト (typed array object) の型の一つです。 基本的には 8ビットの Uint 型 (符号なし整数) で、[0, 255] の範囲の整数を表します。
clamp は固定するとか、締め付けるとかの意味です。[0, 255] の範囲外の値をセットしたときに、その範囲の一番近い整数に寄せられます。
実際に試してみましょう。
var a = new Uint8ClampedArray(4);
a[0] = -10;
a[1] = 1.5;
a[2] = 300;
console.log(a);
この結果 Uint8ClampedArray では次のような値になります。
[0,255] の範囲より小さい値は 0 に、大きい値は 255 になっています。また小数点の値は四捨五入されています。
以上、ここでは JavaScript でバイナリデータを扱う方法として、ArrayBuffer を使う方法と、それに関連した型付き配列オブジェクトについて説明しました。