JavaScript | Web API:ファイル・データ操作 - バイナリデータ処理

JavaScript JavaScript
スポンサーリンク

「バイナリデータ処理」は“0と1の生データ”を意識して触ること

ふだん JavaScript で扱っているのは、
文字列・数値・オブジェクトなど「人間に優しい形」のデータです。

でも、画像・音声・PDF・ZIP などのファイルの正体は、
全部「0と1が並んだ生のバイト列」です。
これをまとめて バイナリデータ と呼びます。

Web の世界でも、
「画像を部分的に解析したい」
「独自フォーマットのファイルを読みたい」
「サーバーから来たバイナリを加工したい」
といった場面では、この“生データ”を直接触る必要が出てきます。

そのときに使うのが、

  • ArrayBuffer(バイト列そのもの)
  • TypedArray(Uint8Array など、バイト列を扱うための配列)
  • DataView(バイト列から「16ビット整数」「32ビット浮動小数」などを読むためのビュー)
  • Blob / File(バイナリをまとめて持つコンテナ)
  • FileReader(Blob / File を読み込む)

といった Web API たちです。

ここでは、
「バイナリって何者?」から始めて、
最低限押さえておきたい流れを例題付きで整理していきます。


ArrayBuffer と TypedArray:バイト列を“配列っぽく”扱うための土台

ArrayBuffer は「連続したバイトの箱」

ArrayBuffer は、
「固定長のバイト列(メモリ領域)」を表すオブジェクト です。

例えば、10バイト分の箱を作るとこうなります。

const buffer = new ArrayBuffer(10);
console.log(buffer.byteLength); // 10
JavaScript

この時点では、
「10バイト分のメモリが確保された」だけで、
中身を直接読むことはできません。

ArrayBuffer はあくまで「生のメモリ」であって、
それをどう解釈するかは、別のオブジェクトに任せます。

TypedArray は「バイト列を特定の型として見るための配列」

Uint8Array などの TypedArray は、
ArrayBuffer を「8ビット符号なし整数の配列」として見るためのビューです。

const buffer = new ArrayBuffer(4);
const bytes = new Uint8Array(buffer);

bytes[0] = 72;  // 'H'
bytes[1] = 101; // 'e'
bytes[2] = 108; // 'l'
bytes[3] = 108; // 'l'

console.log(bytes); // Uint8Array(4) [72, 101, 108, 108]
JavaScript

ここで重要なのは、

  • ArrayBuffer は「生のバイト列」
  • TypedArray は「そのバイト列をどう解釈するか」を決めるビュー

という関係です。

Uint8Array 以外にも、
Int16Array, Float32Array など、
「何ビットで、符号ありか、浮動小数か」を変えたビューがたくさんあります。

例題:文字列 “Hello” をバイト列にしてみる(ざっくり版)

文字列をバイト列に変換するには、
TextEncoder を使うのが簡単です。

const encoder = new TextEncoder(); // UTF-8 エンコーダ
const bytes = encoder.encode("Hello");
console.log(bytes); // Uint8Array(5) [72, 101, 108, 108, 111]
JavaScript

ここで bytes.buffer が ArrayBuffer です。

const buffer = bytes.buffer;
console.log(buffer.byteLength); // 5
JavaScript

この流れで、

文字列 → Uint8Array(バイト列) → ArrayBuffer

という変換ができています。

「文字列も、最終的にはバイト列として表現されている」
という感覚を持てると、一気に理解が進みます。


Blob / File とバイナリ:ファイルの中身をバイト列として扱う

File / Blob を ArrayBuffer として読む

FileBlob の中身をバイナリとして扱いたいときは、
FileReaderreadAsArrayBuffer を使います。

const reader = new FileReader();

reader.addEventListener("load", () => {
  const buffer = reader.result; // ArrayBuffer
  console.log(buffer.byteLength);
});

reader.readAsArrayBuffer(fileOrBlob);
JavaScript

ここで buffer は、
ファイルの中身そのものを表す ArrayBuffer です。

あとは Uint8Array などでビューを作れば、
1バイトずつ中身を見たり、
特定の位置の値を読んだりできます。

const bytes = new Uint8Array(buffer);
console.log(bytes[0], bytes[1], bytes[2]);
JavaScript

例題:画像ファイルの先頭数バイトを覗いてみる

画像ファイルには「マジックナンバー」と呼ばれる
ファイル形式を示すバイト列が先頭に入っています。

例えば PNG なら、先頭 8 バイトは決まった値です。

input.addEventListener("change", () => {
  const file = input.files[0];
  if (!file) return;

  const reader = new FileReader();

  reader.addEventListener("load", () => {
    const buffer = reader.result;
    const bytes = new Uint8Array(buffer);

    const header = bytes.slice(0, 8);
    console.log("先頭8バイト:", Array.from(header));

    // 簡易チェック(PNG のシグネチャ)
    const isPng =
      header[0] === 0x89 &&
      header[1] === 0x50 && // 'P'
      header[2] === 0x4E && // 'N'
      header[3] === 0x47 && // 'G'
      header[4] === 0x0D &&
      header[5] === 0x0A &&
      header[6] === 0x1A &&
      header[7] === 0x0A;

    console.log("PNG っぽい?", isPng);
  });

  reader.readAsArrayBuffer(file);
});
JavaScript

ここでやっていることは、

File を ArrayBuffer として読む
Uint8Array でバイト列として見る
先頭数バイトをチェックして「形式っぽさ」を判定する

という、まさに「バイナリを直接触る」処理です。

実務では、
独自フォーマットや特殊なバイナリプロトコルを扱うときに
こういうことをやります。


DataView:バイト列から「整数」「浮動小数」を読み書きする

なぜ DataView が必要なのか

TypedArray は「全部同じ型の配列」です。
例えば Int32Array なら、
4バイトごとに 32ビット整数として解釈します。

でも、バイナリフォーマットの多くは、

  • 先頭2バイトは長さ(16ビット整数)
  • 次の4バイトはID(32ビット整数)
  • 次の8バイトは座標(64ビット浮動小数)

みたいに、
「いろんな型が混ざっている」ことが多いです。

そこで使うのが DataView です。

DataView は、
「ArrayBuffer から任意の位置にある値を、任意の型として読む/書くためのビュー」 です。

DataView の基本

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

// 先頭2バイトに 1234 を 16ビット整数として書く
view.setUint16(0, 1234);

// 次の4バイトに 3.14 を 32ビット浮動小数として書く
view.setFloat32(2, 3.14);

// 読み出し
const n = view.getUint16(0);
const f = view.getFloat32(2);

console.log(n, f);
JavaScript

getUint16, getInt32, getFloat64 など、
いろんな型の getter / setter が用意されています。

また、多くのバイナリフォーマットでは
「ビッグエンディアン/リトルエンディアン」の指定が必要になるので、
第3引数で指定できます(初心者のうちは「そういうのがある」程度でOK)。

例題:簡単な独自バイナリフォーマットを作ってみる

例えば、
「先頭2バイトが年齢、次の4バイトが身長(cm)」
という超シンプルなバイナリを作るとします。

function createPersonBinary(age, height) {
  const buffer = new ArrayBuffer(6);
  const view = new DataView(buffer);

  view.setUint16(0, age);      // 0〜1バイト目
  view.setFloat32(2, height);  // 2〜5バイト目

  return buffer;
}

function readPersonBinary(buffer) {
  const view = new DataView(buffer);

  const age = view.getUint16(0);
  const height = view.getFloat32(2);

  return { age, height };
}

const buf = createPersonBinary(25, 172.5);
const person = readPersonBinary(buf);
console.log(person); // { age: 25, height: 172.5 }
JavaScript

これはかなり単純化した例ですが、

ArrayBuffer に対して
DataView で「どの位置に」「どの型で」書くかを決める
同じルールで読み出せば、元の情報が復元できる

という「バイナリフォーマット設計」の感覚が少し見えてきます。


バイナリとテキストの行き来:TextEncoder / TextDecoder

文字列 → バイト列(エンコード)

さっき少し出てきた TextEncoder は、
文字列を UTF-8 のバイト列に変換してくれます。

const encoder = new TextEncoder();
const bytes = encoder.encode("こんにちは");
console.log(bytes); // Uint8Array([...])
JavaScript

バイト列 → 文字列(デコード)

逆に、バイト列から文字列に戻すには TextDecoder を使います。

const decoder = new TextDecoder("utf-8");
const text = decoder.decode(bytes);
console.log(text); // "こんにちは"
JavaScript

これで、

文字列 → Uint8Array → ArrayBuffer
ArrayBuffer → Uint8Array → 文字列

という往復ができます。

「バイナリの中にテキストが埋まっている」
「サーバーからバイナリで来たけど中身は JSON」
みたいなケースでは、この往復がよく登場します。


例題:fetch でバイナリを取得して、テキストとして読む

サーバーからバイナリレスポンスを受け取る

async function fetchBinary() {
  const res = await fetch("/api/data");
  const buffer = await res.arrayBuffer();

  const bytes = new Uint8Array(buffer);
  console.log("バイト数:", bytes.length);

  const decoder = new TextDecoder("utf-8");
  const text = decoder.decode(bytes);
  console.log("中身:", text);
}
JavaScript

ここでは、

res.arrayBuffer() でレスポンスを ArrayBuffer として取得
Uint8Array でバイト列として見る
TextDecoder で文字列に戻す

という流れになっています。

res.text() でも文字列は取れますが、
「一度バイナリとして触ってからテキストに戻す」
という感覚を持っておくと、
より低レベルな処理にも対応できるようになります。


初心者として「バイナリデータ処理」で本当に掴んでほしいこと

ここまでを一気に全部マスターする必要はありません。
ただ、次の感覚だけはしっかり持っておいてほしいです。

バイナリデータとは「0と1が並んだ生のバイト列」のこと
ArrayBuffer は「バイト列そのもの」、TypedArray / DataView は「それをどう見るか」を決めるビュー
File / Blob の中身も、最終的には ArrayBuffer として扱える
FileReader の readAsArrayBuffer や fetch の res.arrayBuffer() で「バイナリとして読む」入口に立てる
TextEncoder / TextDecoder で「文字列 ↔ バイト列」の変換ができる

まずは、

文字列を TextEncoder でバイト列にして、TextDecoder で戻す
画像ファイルを readAsArrayBuffer で読み、先頭数バイトを Uint8Array で覗いてみる

この 2 つを自分の手で書いてみてください。

「すべてのデータは、最終的にはバイト列として表現されている」
という感覚が一度でも腹に落ちると、
バイナリデータ処理は一気に“怖いもの”から
「低レベルだけど、ちゃんと筋の通った世界」 に変わっていきます。

タイトルとURLをコピーしました