JavaScript アドバンス

JavaScript DataView

1. DataViewとは?

DataViewオブジェクトは、ArrayBuffer内の複数の数値型を、任意のバイトオフセット(位置)で、さらにエンディアン(バイトオーダー)を任意に制御しながら読み書きするための低レベルインターフェースです。

DataViewは、ArrayBufferの上に構築されるビュー(View)の一種です。
DataView自体はデータを保持しません。代わりに、バッファ内のバイトを以下のような異なる型として解釈することを可能にします。

  • 8ビット、16ビット、32ビット整数(符号あり・符号なし)
  • 32ビットおよび64ビット浮動小数点数
  • バッファ内の任意のバイトオフセットでの操作

リトルエンディアン(Little-endian)またはビッグエンディアン(Big-endian)の切り替え設定

DataViewを使用すべきケース:

  • 異なる型やサイズのフィールドが混在するバイナリフォーマットを扱う場合。
  • バイトオーダー(エンディアン)を厳密に制御する必要がある場合。
  • Uint8ArrayやFloat32Arrayといった型付き配列(Typed Arrays)では柔軟性が不足する場合。

DataViewは、ネットワークパケットや独自のファイル形式、あるいは他の言語との相互運用性など、バイトレイアウトや型を完全にコントロールする必要があるバイナリデータの処理において、その真価を発揮します。

2. DataViewの作成

DataViewを作成するには、まずArrayBufferが必要です。
その後、そのArrayBufferをnew DataView()コンストラクタに渡します。

// 16バイトのArrayBufferを作成
const buffer = new ArrayBuffer(16);

// 新しいDataViewを作成
const view = new DataView(buffer);

// 長さとオフセットを取得
let len1 = buffer.byteLength;
let len2 = view.byteLength;
let off1 = view.byteOffset;

また、特定のオフセットから開始し、長さを制限したDataViewを作成することも可能です。

例(オフセット4から開始し、長さ8で作成):

// 16バイトのArrayBufferを作成
const buffer = new ArrayBuffer(16);

// 新しいDataViewを作成(オフセット4、長さ8)
const view = new DataView(buffer, 4, 8);

// 長さとオフセットを取得
let len1 = buffer.byteLength;
let len2 = view.byteLength;
let off1 = view.byteOffset;

複数のDataViewや型付き配列のビューが、同じArrayBufferを共有することができます。

3. DataViewによる値の読み取りと書き込み

DataViewは、値の読み書きにgetXxx()およびsetXxx()メソッドを使用します。

  • getInt8(), setInt8()
  • getUint16(), setUint16()
  • getInt32(), setInt32()
  • getFloat32(), setFloat32()
  • その他

例(getInt32およびsetInt32):

const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);

// バイトオフセット0に32ビット符号あり整数を書き込む
view.setInt32(0, 123456);

// 値を読み取る
const value = view.getInt32(0);

console.log(value); // 123456

例(getFloat64およびsetFloat64):

const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);

// 円周率とネイピア数を書き込む
view.setFloat64(0, Math.PI);
view.setFloat64(8, Math.E);

const pi = view.getFloat64(0);
const e = view.getFloat64(8);

console.log("円周率:", pi);
console.log("ネイピア数:", e);

       注意: 第1引数は常に、基盤となるバッファ(またはビューのbyteOffset)の開始点からのバイトオフセットです。

4. エンディアン(バイトオーダー)

多くのバイナリフォーマットでは、マルチバイトの数値がリトルエンディアンまたはビッグエンディアンのどちらで格納されるかが指定されています。

  • リトルエンディアン: 最下位バイトが先にくる(PCで最も一般的)。
  • ビッグエンディアン: 最上位バイトが先にくる(特定のネットワークプロトコルなどで一般的)。

DataViewのほとんどの数値メソッドには、オプションのlittleEndianパラメーターがあります。

  • true = リトルエンディアン
  • false(または省略時) = ビッグエンディアン

例:リトルエンディアンとビッグエンディアンでの書き込み

const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);

// 同じ数値でも、バイトオーダーが異なると格納形式が変わる:
view.setUint32(0, 0x12345678, true); // リトルエンディアン

console.log(new Uint8Array(buffer));
// 出力例: Uint8Array(4) [120, 86, 52, 18] (0x78, 0x56, 0x34, 0x12)

// ビッグエンディアンで上書き
view.setUint32(0, 0x12345678, false); // ビッグエンディアン

console.log(new Uint8Array(buffer));
// 出力例: Uint8Array(4) [18, 52, 86, 120] (0x12, 0x34, 0x56, 0x78)

ヒント: 使用するファイル形式やプロトコルの仕様書を確認し、どちらのエンディアンを使用すべきかを必ずチェックしてください。

5. 単一バッファ内でのデータ型の混在

DataViewの主な利点の一つは、単一のバッファ内で異なる型を混在させることができる点です。例えば、以下のような構造が可能です:

  • フラグ用に1バイト
  • 長さ(サイズ)用に2バイト
  • 浮動小数点数用に4バイト

例:データ型の混在

const buffer = new ArrayBuffer(12);
const view = new DataView(buffer);

// バイト0: フラグ (0 または 1)
view.setUint8(0, 1);

// バイト1-2: 16ビットの長さ (リトルエンディアン)
view.setUint16(1, 500, true);

// バイト4-11: 64ビット浮動小数点数 (アライメントのためバイト3をスキップ)
view.setFloat64(4, 3.14159, true);

// 値を読み戻す
const flag = view.getUint8(0);
const length = view.getUint16(1, true);
const value = view.getFloat64(4, true);

console.log("フラグ:", flag);
console.log("長さ:", length);
console.log("値:", value);

6. まとめ

  • DataViewは、ArrayBuffer内の複数の数値型を読み書きするための柔軟な手段です。
  • getXxx()およびsetXxx()メソッドを使用し、バイトオフセット(および必要に応じてエンディアン)を指定します。
  • バイナリプロトコル、ファイルフォーマット、他言語とのデータ交換に最適です。
  • DataViewと型付き配列を組み合わせることで、個々のバイトを簡単に検査できます。