【JavaScript】バイナリデータ理解への旅

# はじめに
バイナリデータを扱うときにちょこちょこ出てくる、Buffer, Blob, Base64, ...
出てくるたびに少し調べて、わかった気になって、忘れたころにまたやってくる...
お前ら一体なんなんだよぉと思ったので、ちゃんとアウトプットしてみた。

# そもそもバイナリって？

[IT用語辞典](https://garop.com/68/)によれば、以下とのこと。
> バイナリ（binary）とは、一般には、二進数もしくは0と1で表現される数字のこと。

しかし、曖昧な使われ方をしているようで、人間に向けではないコンピュータ用のデータを総じてバイナリデータといったりするみたい。

# 型付き配列

JavaScriptの世界で、通常の配列は色々な値を入れられるし、拡張なども自由で非常に便利。
しかし、巨大な**バイナリ**データなどを扱うときは処理速度遅い。。。
→ **型付き配列**というものを使おう！！！

:::note info
* 通常の配列：型の制限無く自由な値を格納可能。要素数の拡張なども自由。
* **型付き配列**：格納する値の型や機能に制限あり。→ **高速データアクセス**
:::

この型付き配列を扱う時に、**バッファー**と**ビュー**というものを使う！！

|            | 役割           | 実装                           | 
| ---------- | -------------- | ------------------------------ | 
| バッファー | メモリの確保   | ArrayBufferクラス              | 
| ビュー     | 値へのアクセス | TypedArrayオブジェクト、DataView |

※DataViewはここでは割愛します。

## ArrayBufferクラス

文字通り、バイトの配列。以下のように書けば、16バイトの配列。
```javascript
const buffer = new ArrayBuffer(16);
```
ArrayBuffer の内容（確保したメモリ）を直接操作することはできない。
なら、どうすんねん。→ ビューを使う！！！

## TypedArrayオブジェクト

:::note warn
TypedArrayコンストラクタがあるわけではない！！
:::

実際にあるのは、以下のようなサブクラス（ほかにもいろいろある）。

| 型          | 値の範囲        | バイト数 | 
| ----------- | --------------- | -------- | 
| Int8Array   | -128 ～ 127     | 1        | 
| Uint8Array  | 0 ～ 255        | 1        | 
| Int16Array  | -32768 ～ 32767 | 2        | 
| Uint16Array | 0  ～ 65535     | 2        | 

Int8: 8bit（1バイト）の整数型。
Uint = Unsigned int なので、符号なし整数型のこと。

ArrayBufferでメモリを確保し、確保したメモリに対して、Uint8Arrayでビューを作成しアクセスしてみる。
```javascript
const buffer = new ArrayBuffer(16); // 16バイト
const uint8Array = new Uint8Array(buffer, 0, 3) // 第2引数: byteOffset, 第3引数: length
console.log(uint8Array)
// Uint8Array(16) [0, 0, 0]
uint8Array[0] = 255
uint8Array[1] = 257
uint8Array[2] = 'hoge'
console.log(uint8Array)
// Uint8Array(16) [255, 1, 0]
```
上記から、Uint8Arrayオブジェクト作成直後は、各要素は0で初期化されており、255までの整数を設定できるが、範囲外の値の場合は、下位ビットの値のみが採用されていることがわかる。また、文字列を設定しようとしてもエラーにはならないが無視されるみたい。

## 型付き配列は、通常配列とどう違うの？

* Array.isArrayメソッドでfalseとなる
* pushやpopなどの一部のメソッドが使えない
```javascript
const normalArray = [0, 1];
const buffer = new ArrayBuffer(16);
const typedArray = new Int16Array(buffer);

console.log(Array.isArray(normalArray));
// true
console.log(Array.isArray(typedArray));
// false

normalArray.push(2);
console.log(normalArray);
// [0, 1, 2]
typedArray.push(2);
// TypeError: typedArray.push is not a function
```

## 型付き配列の方が通常配列より速いってほんとに～？

下記のようなスクリプトを用意する。
```javascript
const TRIAL_COUNT = 3;
const ARRAY_LENGTH = 1024*1024*50;
const DIVISOR = 200; // 各要素に格納する値は0~200になるようにする

//Array:
for(let j = 0; j < TRIAL_COUNT; j++) {
  const startTime = Date.now();
  let arr = Array(ARRAY_LENGTH);
  for(let i = 0; i < arr.length; i++) {
    arr[i] = i % DIVISOR;
  }
  let sum = 0;
  for(let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  console.log('Array:', Date.now() - startTime, '[ms] , length:', arr.length, ', Sum:', sum);
  arr = undefined;
}
//Uint8Array
for(let j = 0; j < TRIAL_COUNT; j++) {
  const startTime = Date.now();
  let arr = new Uint8Array(ARRAY_LENGTH);
  for(let i = 0; i < arr.length; i++) {
    arr[i] = i % DIVISOR;
  }
  let sum = 0;
  for(let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  console.log('Uint8Array:', Date.now() - startTime, '[ms] , length:', arr.length, ', Sum:', sum, ', byteLen:', arr.byteLength);
  arr = undefined;
}
//Float64Array:
for(let j = 0; j < TRIAL_COUNT; j++) {
  const startTime = Date.now();
  let arr = new Float64Array(ARRAY_LENGTH);
  for(let i = 0; i < arr.length; i++) {
    arr[i] = i % DIVISOR;
  }
  let sum = 0;
  for(let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  console.log('Float64Array:', Date.now() - startTime, '[ms] , length:', arr.length, ', Sum:', sum, ', byteLen:', arr.byteLength);
  arr = undefined;
}
```
実行すると、以下のように結果が得られた。
```javascript
Array: 4561 [ms] , length: 52428800 , Sum: 5216665600
Array: 3715 [ms] , length: 52428800 , Sum: 5216665600
Array: 3761 [ms] , length: 52428800 , Sum: 5216665600
Uint8Array: 160 [ms] , length: 52428800 , Sum: 5216665600 , byteLen: 52428800
Uint8Array: 175 [ms] , length: 52428800 , Sum: 5216665600 , byteLen: 52428800
Uint8Array: 151 [ms] , length: 52428800 , Sum: 5216665600 , byteLen: 52428800
Float64Array: 138 [ms] , length: 52428800 , Sum: 5216665600 , byteLen: 419430400
Float64Array: 154 [ms] , length: 52428800 , Sum: 5216665600 , byteLen: 419430400
Float64Array: 157 [ms] , length: 52428800 , Sum: 5216665600 , byteLen: 419430400
```
この結果から以下が確認できる。

:::note info
* 型付き配列の方が、通常配列よりも速い
* Float64とUint8では、速度はほとんど変わらない
* byteLenを見ると、Uint8Arrayの方がFloat64Arrayより少ないメモリで済んでいる
:::

## TypedArrayオブジェクト結局どれを使えばいいん？

**Uint8ArrayでOK！**

理由：
- ほとんどのバイナリファイルデータが8ビットの符号なし整数（バイト）単位で構成されている
- 必要最低限のビット幅をもつTypedArrayを使う方がメモリ効率がよい

# Blob ってなに？

[Blob（Binary Large Object）](https://developer.mozilla.org/ja/docs/Web/API/Blob)は、不変の生データであるファイルのようなバイナリデータの大きな塊を効率的に扱うためのオブジェクトらしい。

Blobは以下のように作成できる。Blobコンストラクタには、配列を渡す。配列の中身としては、文字列、ArrayBuffer, TypedArray, Blob などを入れる。
```javascript
const obj = { hello: "world" };
const blob = new Blob([JSON.stringify(obj, null, 2)], {
  type: "application/json",
});
```

## Blob と File

下記のソースコードは、ファイルをアップロードすると、そのファイルを参照できるURLを生成し、画面表示する。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="VwOqGyd" data-pen-title="Untitled" data-user="Yucca1" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yucca1/pen/VwOqGyd">
  Untitled</a> by Yucca1 (<a href="https://codepen.io/Yucca1">@Yucca1</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

- 上記のJavaScriptソースコードにおける、`event.target.files[0]`で取得できるものが [File](https://developer.mozilla.org/ja/docs/Web/API/File) オブジェクト。
- **Fileオブジェクトは、特別な種類のBlobオブジェクト**。Blobが利用できる場面ではどこでも利用できる。
- 生成されるURLは、バイナリデータを保持していて、[URL.createObjectURL](https://developer.mozilla.org/ja/docs/Web/API/URL/createObjectURL_static) を用いてFileオブジェクトを変換して取得している。

## Blob と ArrayBuffer の使い分け

バイナリデータを扱う方法として、BlobとArrayBufferがあることはわかったけど、使い分けはどう考えればいいのか。どうやら以下のように考えてよさそう

:::note info
* Blob: ファイルをそのまま操作
* ArrayBuffer：ファイルの中身をみてみる
:::

例えば、二つのファイルをアップロードし、それらを結合して、ダウンロードするソースを書いてみると以下の通り。ここでは、ファイルのアップロード、ダウンロードはBlobで行い、ファイルの結合はArrayBufferを用いて実施している。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="xxNoJBJ" data-pen-title="BlobSample02" data-user="Yucca1" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yucca1/pen/xxNoJBJ">
  BlobSample02</a> by Yucca1 (<a href="https://codepen.io/Yucca1">@Yucca1</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


# Base64 とは？

- [Base64](https://developer.mozilla.org/ja/docs/Glossary/Base64)は、バイナリデータをテキスト形式で表現するためのエンコーディング方法
- バイナリデータを64種類のASCII文字（A-Z、a-z、0-9、+、/）で表現するため、テキストとして扱える

## Base64 によるデータ転送

HTTPリクエスト・レスポンスボディでバイナリデータを送受信する場合に、Base64エンコーディングを使う。ここでは、HTTPリクエストでファイルデータを送信する準備として、画面上でアップロードしたファイルデータをBase64エンコーディングするところまでやってみる。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="LYowPmx" data-pen-title="BlobSample03" data-user="Yucca1" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yucca1/pen/LYowPmx">
  BlobSample03</a> by Yucca1 (<a href="https://codepen.io/Yucca1">@Yucca1</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

上記コードでは、[FileReader](https://developer.mozilla.org/ja/docs/Web/API/FileReader) オブジェクトを作成し、`readAsArrayBuffer`メソッドを用いることで、ファイルの内容をArrayBufferとしてresult属性にセットしている。
また、[btoa](https://developer.mozilla.org/ja/docs/Web/API/Window/btoa)メソッド（binary to ASCII）により、バイナリ文字列をBase64エンコーディングしている。

## Base64 によるデータ埋め込み

画像をBase64エンコードしてHTMLの<img>タグのsrc属性に直接埋め込むこともできる。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="LYowPmx" data-pen-title="BlobSample03" data-user="Yucca1" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Yucca1/pen/LYowPmx">
  BlobSample03</a> by Yucca1 (<a href="https://codepen.io/Yucca1">@Yucca1</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

上記コードでは、FileReaderオブジェクトの`readAsDataURL`メソッドを用いることで、ファイルの内容を[データURL](https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)としてresult属性にセットしている。

# 参考文献

- [第6回 JavaScriptの基礎「配列」](https://neqto.jig-saw.com/ja/blog/development/neqtojs-part6-arrays/)
- [JavaScriptの型付き配列](https://azisava.sakura.ne.jp/programming/0024.html)


# おわりに

人類は思い出した...バイナリは面倒ってことを...

とはいえ、基本的なことから学び、いろんなソースコードを書いてみて、また少しバイナリの扱い方について理解を深められた気がする。