Uint8ArrayやArraryBufferやBufferとか
JavaScriptでbinaryを表現するものとしてUint8Array / ArraryBuffer / Buffer
とか色々あってそれぞれどう違うんだ?と思ったので改めて調べてみました。
それぞれの概念について
まずは、それぞれの概念について軽く調べていきます
ArrayBuffer
ArrayBuffer
はNode.js環境でもブラウザ環境でもどちらでも使用することが可能で、生のバイナリーデータバッファーを表現するために使用されます。
const arrayBuffer = new ArrayBuffer(8);
console.log(arrayBuffer);
// 出力
// ArrayBuffer {
// [Uint8Contents]: <00 00 00 00 00 00 00 00>,
// byteLength: 8
// }
物理メモリ上に指定した領域を確保するだけで、基本的には参照を渡すことしかできません。maxByteLength
オプションを指定してサイズの変更は可能です。
ArrayBuffer
を用いて確保した領域に対し実際のbinaryを確保するにはTypedArray
と呼ばれるビューを使用する必要があります。Uint8Array
はビューの中の1つです。
Uint8Array
Uint8Array
もNode.js環境でもブラウザ環境でもどちらでも使用することが可能です。ArrayBuffer
のビューであり指定したbyteの数値だけ0で初期化します。
const uInt8Array = new Uint8Array(8);
console.log(uInt8Array);
// 出力
// Uint8Array(8) [
// 0, 0, 0, 0,
// 0, 0, 0, 0
// ]
binaryを操作するためのインターフェースを提供しているため、Uint8Array
を用いると下記のようにbinaryの操作が可能です。
// Uint8Arrayの作成(16進数で"Hello, world"と記述しています)
const uint8Array = new Uint8Array([
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
]);
uint8Array[1] = 0x6f; // Uint8Arrayの要素を変更
// TextDecoderでUint8Arrayを文字列に変換
const decoder = new TextDecoder("utf-8");
const decodedString = decoder.decode(uint8Array);
console.log(decodedString); // 出力: "Hollo, world"
ちなみにUint8Array
などのTypedArray
はArrayBuffer
の参照を操作しているため、下記のように記述するとArrayBuffer
の値が変わっているのがわかります。
const arrayBuffer = new ArrayBuffer(8);
const uint8Array = new Uint8Array(arrayBuffer);
uint8Array[0] = 100;
console.log(arrayBuffer);
// 出力
// ArrayBuffer {
// [Uint8Contents]: <64 00 00 00 00 00 00 00>,
// byteLength: 8
// }
console.log(uint8Array);
// 出力
// Uint8Array(8) [
// 100, 0, 0, 0,
// 0, 0, 0, 0
// ]
Buffer
Buffer
はNode.js専用のクラスでUint8Array
のサブクラスとして定義されており、更なるユースケースをカバーすることができるようになっています。
バイナリーデータの作成はもちろん、操作、読み込みなどバイナリデータを直接扱う方法を提供します。
ちなみに今はBuffer()
を用いてBufferオブジェクトを生成することは非推奨とされているため、Buffer.from()
などを用いて生成します。
const buffer = Buffer.from("Hello, World!");
console.log(buffer);
// <Buffer 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21>
また、toString()
を使えばBufferオブジェクトから元の文字列を生成できます。
const buffer = Buffer.from("Hello, World!");
console.log(buffer.toString());
// Hello, World!
wirte()
を使うとバイナリデータの操作も可能です。
const buffer = Buffer.from("Hello, World!");
buffer.write("o", 1);
console.log(buffer.toString());
// "Hollo, World!"
実践
webサービスでbinaryと聞くと主に画像の扱いを自分は連想するので(普段触っているものが何かにもよりますね)今回はfetchも絡めて実際にwebから画像をpostして、どのようなデータでやり取りされてるのかを確認してみます。
まずは、local serverをexpressで立ち上げるためのsetupです。ミドルウェアとしてmulter
を使用します。
const express = require("express");
const cors = require("cors");
const multer = require("multer");
const app = express();
const port = 3000;
app.use(cors());
const upload = multer({ storage: multer.memoryStorage() });
app.post("/upload", upload.single("file"), (req, res) => {
console.log(req.file);
res.send("Success");
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
次にhtmlです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>画像処理の例</title>
</head>
<body>
<input type="file" id="fileInput" accept="image/*" />
<script>
const fileInput = document.getElementById("fileInput");
const canvas = document.getElementById("canvas");
fileInput.addEventListener("change", (event) => {
const file = event.target.files[0];
const form = new FormData();
form.append("file", file);
fetch("http://localhost:3000/upload", {
method: "POST",
body: form,
}).then((response) => {
console.log(response, "response");
});
});
</script>
</body>
</html>
まず、ここの出力を確認してみるとFile objectのデータを確認することができます。
const file = event.target.files[0];
File interfaceはBlobをベースにしており、Blobの機能を継承してユーザーのシステム上のファイルをサポートするように拡張されています。
次に実際にpostされたデータを見てみます。Middlewareとしてmulterを使用しているため下記はmulter obectとなります。中身を見てみるとbuffer
プロパティの中にBuffer
オブジェクトが格納されています。
{
fieldname: 'file',
originalname: 'ã\x82¹ã\x82¯ã\x83ªã\x83¼ã\x83³ã\x82·ã\x83§ã\x83\x83ã\x83\x88 2024-06-21 11.54.45.png',
encoding: '7bit',
mimetype: 'image/png',
buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 07 20 00 00 02 a2 08 06 00 00 00 9e a6 cb 8d 00 00 0a aa 69 43 43 50 49 43 43 20 50 72 6f 66 69 ... 252759 more bytes>,
size: 252809
}
では、実際にwebからpostされたfileがmulterを介してどのようにBuffer
になっているのかを見てみましょう。entry pointはここです。
下記のstorageによる分岐で処理が変わります。今回はmemoryStorage
を選択しています。
if (options.storage) {
this.storage = options.storage
} else if (options.dest) {
this.storage = diskStorage({ destination: options.dest })
} else {
this.storage = memoryStorage()
}
memoryStorage
の処理はこちらです。_handleFile
の中でstream.pipe()が実行されていおり、concatを使用しstreamを連結しています。
そして、callbackとしてdataを受け取り、buffer
に格納します。
最後はmake-middleware.js
で_handleFile
が実行されます。
最後に
最後はmulterのコードリーディングになってしまいましたが、Uint8Array / ArraryBuffer / Buffer
をまとめてみました。次はStream
についてもう少し詳しく調査してみようと思います。
検証として使ったrepositoryは下記にあります。
参考
Discussion