JavaScriptでバイナリーを扱う

Author:

この記事では、JavaScriptでバイナリーファイルを16進数テキストに変換したり、逆に16進数テキストからバイナリーファイルを作成するといったバイナリーデータに対し基本的な操作を行います。
文字列や数値などの基本的なデータ型だけでなく、バイナリーデータを含むJavaScriptプログラムが作成できるようになれば、プログラムの幅が広がります。是非バイナリーデータの取り扱いをマスターしましょう😀。



バイナリーデータを16進数で表現する

バイナリーデータは、ビット配列を0と1が連続する2進数で表現されるデータを指し、編集しやすいように4ビットずつ16進数で表示することが可能です。
バイナリーデータは、メモリー内で8ビット(1バイト)単位で管理されており、上位4ビット、下位4ビットをそれぞれ16進数で表示するため、1バイトを2文字で表現します。

ビット配列(2進数)と16進数の関係

整数リテラル

JavaScriptでは、プログラムの中で値を表現するためにリテラルを使用します。

  • 先頭が0(ゼロ)ではない一連の数字は、10進数の整数リテラルを意味します。10進数の数値は、先頭に0bや0xが付かない、0から9の数字で構成されます。
  • 先頭の0x(または0X)は、16進数の整数リテラルを意味します。16進数の数値は、先頭の0xに加え、0から9の数字とaからfおよびAからFのアルファベットで構成されます。(大文字小文字の違いは値には影響しない。)
  • 先頭の0b(または0B)は、2進数の整数リテラルを意味します。2進数の数値は、先頭の0bに加え、0と1の数字で構成されます。
ビット配列と2進数および16進数リテラルの対応表

ArrayBufferオブジェクト

バイナリーデータを扱うJavaScriptのオブジェクトとしてArrayBufferBlobがあります。

  • ArrayBufferオブジェクト: 一般的な生のバイナリーデータバッファーを表します。バッファーとは、データを一時的に記憶することを言います。ArrayBufferオブジェクトは、いわゆるバイト配列となります。ArrayBufferオブジェクトのデータを直接操作することはできません。ArrayBufferViewerと呼ばれるTypedArrayオブジェクトまたはDataViewオブジェクトのいずれかを作成して、ArrayBufferの内容を間接的に読み書きします。
  • Blobオブジェクト: Blobは、Binary Large Objectの略です。不変の生データであるファイルのようなオブジェクトを表します。テキストやバイナリーデータをBlobとして読み込んだり、データ処理をしたりするのに使用されます。またファイルを保存する際にもBlobオブジェクトを使用します。

この記事では、バイナリーデータの変更が可能なArrayBufferオブジェクトを使用します。


DataViewオブジェクト

DataViewオブジェクトは、ArrayBufferオブジェクトが保持するバイナリーデータに対して、様々な数値型によるビューを通じてエディアンに関係なく読み書きするための低水準インターフェイスです。
DataViewオブジェクトは、8ビット、16ビット、32ビットおよび64ビットの符号無しおよび符号付きの整数ならびに浮動小数点数に対応しており、オフセット値を指定して任意の位置からデータを読み書きすることができます。
そのため、DataViewオブジェクトは、複雑なバイナリーデータを扱う際に非常に便利です。
ArrayBufferオブジェクトのバッファーサイズを超えて、データの読み書きを行うとエラーになるため、注意する必要があります。

ArrayBufferオブジェクトおよびDataViewオブジェクトの使用例

// 2バイトのArrayBufferオブジェクトを作成
const arrayBuffer = new ArrayBuffer(2);
// ArrayBufferオブジェクトを読み書きするためのArrayBufferViewerを作成
const dataView = new DataView(arrayBuffer);

// 1バイト目を起点に16ビット符号無し16ビット整数(2バイト)を格納
// 10905 = 0x2a99
dataView.setUint16(0, 10905);
// 1バイト目を起点に符号無し16ビット整数(2バイト分のデータ)を取得
console.log(dataView.getUint16(0)); // 10905 (= 0x2a99)
// 1バイト目を起点に符号無し8ビット整数(1バイト分のデータ)を取得
console.log(dataView.getUint8(0)); // 42 (= 0x2a)
// 2バイト目を起点に符号無し8ビット整数(1バイト分のデータ)を取得
console.log(dataView.getUint8(1)); // 153 (= 0x99)

// 1バイト目を起点に符号付き8ビット整数(1バイト分のデータ)を取得
console.log(dataView.getInt8(0)); // 42
// 2バイト目を起点に符号付き8ビット整数(1バイト分のデータ)を取得
console.log(dataView.getInt8(1)); // -103

// 1バイト目を起点に符号無し16ビット整数(2バイト分のデータ)のビッグエンディアン値を取得
console.log(dataView.getUint16(0)); // 10905 (= 0x2a99)
// 1バイト目を起点に符号無し16ビット整数(2バイト分のデータ)のリトルエンディアン値を取得
console.log(dataView.getUint16(0, true)); // 39210(= 0x992a)

バイナリーを扱うJavaScriptの例 1

このJavaScriptの例では、バイナリーを扱うオブジェクトとしてArrayBufferオブジェクト、ArrayBufferVieweとしてDataViewを使用して、バイナリーファイルを16進数テキストに変換するJavaScriptの例を示します。
ポイントとなる部分はソース内にコメントで記載しています。

バイナリーファイルを16進数テキストに変換するJavaScript

完成例

入力: バイナリーファイル


出力: 16進数テキスト


ソース

<!DOCTYPE html>
  <html lang="ja">
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>バイナリーファイルを16進数テキストに変換するJavaScript</title>
    </head>
    <body>
      入力: バイナリーファイル<br />
      <input type="file" id="input" /><br />
      <br />
      出力: 16進数テキスト<br />
      <textarea id="textarea" cols="40" rows="10"></textarea>
      <script>

        // @ts-check

        /**
         * 標準組み込みオブジェクト
         * @typeof {object} ArrayBuffer
         * @typeof {object} DataView
         * Web API
         * @typeof {object} FileList
         * @typeof {object} FileReader
         * @typeof {object} HTMLInputElement
         * @typeof {object} HTMLTextAreaElement
         */

        //------------------------------------------------------------
        // イベント  type="file"のインプット要素でファイルを選択時
        //------------------------------------------------------------
        // ts-check エラー対策 変数を用意し、nullを排除し、HTMLElementではなくHTMLInputElementとして定義
        const htmlInputElement = /** @type {!HTMLInputElement} */ (document.getElementById('input'));
        /** @this HTMLInputElement */
        htmlInputElement.addEventListener('change', function() {

          //------------------------------------------------------------
          // ローカル変数の設定 変更不要
          //------------------------------------------------------------
          // ts-check エラー対策 変数を用意し、nullを排除し、FileListとして定義
          const fileList = /** @type {!FileList} */ (this.files);
          /** @type {FileReader} */
          const reader = new FileReader();

          //------------------------------------------------------------
          // イベント  ファイル読み込み時
          //------------------------------------------------------------
          reader.addEventListener('load', function () {

            //------------------------------------------------------------
            // ローカル変数の設定 変更不要
            //------------------------------------------------------------
            // ts-check エラー対策 変数を用意し、stringおよびnullを排除し、ArrayBufferとして定義
            const arrayBuffer = /** @type {!ArrayBuffer} */ (reader.result);
            /** @type {DataView} ArrayBufferViewを使用 */
            const dataView = new DataView(arrayBuffer);
            // 出力用テキスト
              let outputText = '';

              for (let i = 0; i < arrayBuffer.byteLength; i ++) {
                // .getUint8()メソッドを使用して、1バイトずつ符号無し8ビット整数を取得、1バイトは0 ~ 255の数値となる
                // .toString(16)を使用して、16進数文字列に変換、1バイト分は、0 ~ ffの文字列となる
                // 1バイトを2桁の文字列で表すため、1文字の16進数テキストの場合、.padStat()を使用して、10の位を0で埋めるこどで、2文字にする
                // 0, 1, 2, ・・・ d, e, f → 00, 01, 02, ・・・ 0d, 0e, 0f
                // 出力用テキストに追加
                outputText += dataView.getUint8(i).toString(16).padStart(2, '0');
              }

              // テキストエリア要素に出力
              // ts-check エラー対策 変数を用意し、nullを排除し、HTMLElementではなくHTMLTextAreaElementとして定義
              const htmlTextAreaElement = /** @type {!HTMLTextAreaElement} */ (document.getElementById('textarea'));
              htmlTextAreaElement.value = outputText;

            });

            // readAsArrayBufferメソッドは、指定されたBlobまたはFileの内容をArrayBufferオブジェクトとして読み込むために使用される
          // resultには、ファイルのデータを表すArrayBufferが格納される
          reader.readAsArrayBuffer(fileList[0]);

        });

      </script>
    </body>
  </html>

バイナリーを扱うJavaScriptの例 2

このJavaScriptの例では、バイナリーを扱うオブジェクトとしてArrayBufferオブジェクト、ArrayBufferVieweとしてDataViewを使用して、16進数テキストをバイナリーデータに変換して、その後バイナリーファイルを保存するJavaScriptの例を示します。
ポイントとなる部分はソース内にコメントで記載しています。

16進数テキストをバイナリーデータに変換した後、バイナリーファイルを保存するJavaScript

完成例

入力: 16進数テキスト



出力: バイナリーファイル

ソース

<!DOCTYPE html>
  <html lang="ja">
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>16進数テキストをバイナリーに変換後、バイナリーファイルとして保存するJavaScript</title>
    </head>
    <body>
      入力: 16進数テキスト<br />
      <textarea id="textarea" cols="40" rows="10"></textarea><br />
      <button id="button">16進数テキストをバイナリーに変換後、バイナリーファイルとして保存</button><br />
      <br />
      出力: バイナリーファイル
      <script>

        // @ts-check

        /**
         * 標準組み込みオブジェクト
         * @typeof {object} ArrayBuffer
         * @typeof {object} DataView
         * Web API
         * @typeof {object} Blob
         * @typeof {object} BlobPart
         * @typeof {object} HTMLAnchorElement
         * @typeof {object} HTMLButtonElement
         */

        //------------------------------------------------------------
        // ライブラリー
        //------------------------------------------------------------

        /**
         * ファイルに保存
         * @param {(BlobPart[])} blobParts Arrayオブジェクトなどの反復可能オブジェクト
         * @param {string} type blobに格納されるデータのMIMEタイプ
         * @param {string} fileName ファイル名
         */
        function saveFile(blobParts, type, fileName) {
          /** @type {Blob} */
          const blob = new Blob(blobParts, {
            type: type
          });
          const blobUrl = URL.createObjectURL(blob);
          /** @type {HTMLAnchorElement} */
          const a = document.createElement('a');
          a.download = fileName;
          a.href = blobUrl;
          a.click();
          URL.revokeObjectURL(blobUrl);
        }

        //------------------------------------------------------------
        // イベント  input要素クリック時
        //------------------------------------------------------------
        // ts-check エラー対策 変数を用意し、nullを排除し、HTMLElementではなくHTMLButtonElementとして定義
        const htmlButtonElement = /** @type {!HTMLButtonElement} */ (document.getElementById('button'));
        htmlButtonElement.addEventListener('click', function() {

          //------------------------------------------------------------
          // ローカル変数の設定 変更不要
          //------------------------------------------------------------
          // ts-check エラー対策 変数を用意し、nullを排除し、HTMLElementではなくHTMLTextAreaElementとして定義
          const htmlTextAreaElement = /** @type {!HTMLTextAreaElement} */ (document.getElementById('textarea'));
          // 入力用テキスト
          const INPUT_TEXT = htmlTextAreaElement.value;
          /** @type {ArrayBuffer} ArrayBufferオブジェクトの生成 */
          const arrayBuffer = new ArrayBuffer(INPUT_TEXT.length / 2);
          /** @type {DataView} ArrayBufferViewを使用 */
          const dataView = new DataView(arrayBuffer);

          // ArrayBufferオブジェクトにバイナリーデータを格納
          for (let i = 0; i < arrayBuffer.byteLength; i ++) {
            // slice()を使用して、16進数文字列の入力テキストからバイト位置に対応する2文字分を抽出、1バイト分は、00 ~ ffの文字列となる
            // parseInt()を使用して、16進数文字列から数値に変換、1バイトは0 ~ 255の数値となる
            // setUint8()を使用して、ArrayBufferのバイト位置に数値を格納
            dataView.setUint8(i, parseInt(INPUT_TEXT.slice(i * 2, (i + 1) * 2), 16));
          }

          // ファイルに保存
          saveFile([arrayBuffer], 'application/octet-stream', 'BinaryFile.bin');

        });

      </script>
    </body>
  </html>

ArrayBufferオブジェクトの結合

ArrayBufferオブジェクトは、ArrayBuffer.prototype.slice()メソッドにより、分割することができますが、ArrayBufferを結合するメソッドが用意されていません。
このJavaScriptの例では、バイナリーデータを16進数テキストに変換および16進数テキストをバイナリーデータに変換するライブラリーを使用して、複数のArrayBufferオブジェクトを結合します。
結合だけでなく、バイナリーデータの追加や挿入なども簡単にできるのでお勧めです。

ArrayBufferオブジェクトの結合

// @ts-check

/**
 * 組み込みオブジェクト
 * @typeof {object} ArrayBuffer
 * @typeof {object} DataView
 */

//------------------------------------------------------------
// ライブラリー
//------------------------------------------------------------

/**
 * バイナリーデータ(ArrayBufferオブジェクト)を16進数テキストに変換
 * @param {ArrayBuffer} arrayBuffer 16進数テキストに変換するArrayBufferオブジェクトによるバイナリーデータ
 * @return {string} 変換された16進数テキスト
 */
function arrayBufferToHexText(arrayBuffer) {
  /** @type {DataView} */
  const dataView = new DataView(arrayBuffer);
  let hexText = '';
  for (let i = 0; i < arrayBuffer.byteLength; i ++) {
    hexText += dataView.getUint8(i).toString(16).padStart(2, '0');
  }
  return hexText;
}

/**
 * 16進数テキストをArrayBuffer(バイナリーデータ)に変換
 * @param {string} hexText ArrayBufferオブジェクトによるバイナリーデータに変換する16進数テキスト
 * @return {ArrayBuffer} 変換されたArrayBufferオブジェクト
 */
function hexTextToArrayBuffer(hexText) {
  /** @type {ArrayBuffer} */
  const arrayBuffer = new ArrayBuffer(hexText.length / 2);
  /** @type {DataView} */
  const dataView = new DataView(arrayBuffer);
  for (let i = 0; i < arrayBuffer.byteLength; i ++) {
    dataView.setUint8(i, parseInt(hexText.slice(i * 2, (i + 1) * 2), 16));
  }
  return arrayBuffer;
}

// 結合するArrayBufferオブジェクトの準備
const ab1 = hexTextToArrayBuffer('0001020304050607');
const ab2 = hexTextToArrayBuffer('08090a0b0c0d0e0f');
const ab3 = hexTextToArrayBuffer('1011121314151617');

// ArrayBufferオブジェクトの結合
const concatenatedArrayBuffer1 = hexTextToArrayBuffer(arrayBufferToHexText(ab1) + arrayBufferToHexText(ab2) + arrayBufferToHexText(ab3));

// 結合したArrayBufferオブジェクトができているか確認
console.log(concatenatedArrayBuffer1.byteLength); // 24

// ArrayBufferオブジェクトの結合(バイトの挿入)
const concatenatedArrayBuffer2 = hexTextToArrayBuffer(arrayBufferToHexText(ab1) + '18191a1b' + arrayBufferToHexText(ab2));

// 結合したArrayBufferオブジェクトができているか確認
 console.log(concatenatedArrayBuffer2.byteLength); // 20

参照


関連情報


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です