ArrayBufferって何なんだろう。
経緯
クライアントからbase64形式で送られてきた情報をサーバーで復元し保存するという実装をLLMに教えてもらった。
const fileBuffer = Buffer.from(base64Data, "base64");
const uint8Array = new Uint8Array(fileBuffer);
return new File([uint8Array], name, { type: contentType });
Buffer
、ArrayBuffer
、Base64
、uint8Array
など見覚えはあるけれど、どんなもの達なのかしっかりと理解できていないので調べる。
そもそもbase64形式って何?
自分語り
Flutter Unity Widget
やUnity as a Library
でネイティブアプリとUnity間でやりとりする時に多用した記憶がある。
何となーく「どんな情報でもアルファベットとか数字で構成される人間には読みづらい文字列に変換できるやつ」と認識していたが、以下の通りその理解は大きく外れていなかった。
base64とは、64進数を意味する言葉で、すべてのデータをアルファベット(a~z, A~z)と数字(0~9)、一部の記号(+,/)の64文字で表すエンコード方式です
ただ、データ長を揃えるためにパディングとして末尾に記号の=を使用するので、厳密にはbase64は、65文字の英数字で表現されます[1]
今回の実装ではテキストデータを経由してファイルをクライアントからサーバーに送るためにbase64文字列に変換している。
これは通信量削減のための圧縮のためなのだろうか?と考えていたがどうやら違うらしい。[2]
LLMにその理由を尋ねると以下の内容が返ってきた。(別スレッドのため、実装時の意図を汲み取れているかは不明)
LLMからの回答
クライアントからサーバーにファイルを「テキストデータを経由して」送信する場合、バイナリのままでは扱えないケースが多いため Base64エンコード がよく使われます。理由は以下の通りです。
🔑 Base64に変換する理由
-
テキスト専用のプロトコルに載せるため
- HTTPの古典的なフォーム送信(
application/x-www-form-urlencoded
やmultipart/form-data
)、JSON、XML などは「テキストデータ」としてやり取りする前提。 - そのままバイナリを入れると、制御文字や不正なバイト列でパースに失敗する可能性がある。
- HTTPの古典的なフォーム送信(
-
文字化けやデータ破損を防ぐ
- バイナリには NULL文字 (0x00) や 制御コード が含まれることがある。
- これらはテキスト処理系(データベース、ログ、通信経路の途中のゲートウェイなど)が解釈して壊すことがある。
- Base64はASCII文字(A–Z, a–z, 0–9, +, /)と
=
のみを使うので安全に扱える。
-
互換性の確保
- 古いシステムや中間の通信装置(プロキシ、メールサーバーなど)がバイナリデータをサポートしないことがある。
- 例: メールのMIME規格では添付ファイルをBase64でエンコードして送るのが標準。
⚠️ デメリット
- データサイズが約33%増える(3バイトが4文字に変換されるため)。
- エンコード・デコードにCPUコストがかかる。
🚀 代替方法
-
直接バイナリ送信
- HTTPの
multipart/form-data
でファイルを送る。 - gRPCやWebSocketのバイナリフレームを使う。
- HTTPの
-
Base64を避けられるなら避けるのが効率的。
👉 まとめると、
「テキストしか扱えない経路(JSON, XML, URLエンコード, メールなど)でファイルを送る必要がある場合」に、バイナリの安全性と互換性を確保するためにBase64に変換する、というのが理由です。
ふむふむ、どうやらファイルは「バイナリ」で、それをそのままテキストとして解釈させると色々と不都合があるが、base64文字列に変換することでそれを回避できるらしい。
でも「バイナリ」って何?
バイナリって何?
これはもう「なんか聞いたことある」という次元。とりあえず人間には読めなそう。
バイナリ(binary)とは、コンピュータで取り扱うデータの記述形式の一つで、テキスト(文字)形式ではないもの。実行形式のプログラム、画像、音声、動画などがこの形式で記述されることが多い。[3]
やっぱり読めそうにありませんでした。コンピュータは全ての情報を2進数の「0」と「1」が並んだビット列として表現しているが、何らかの文字コード規格(ASCIIとかUTF-8とか)に基づいて文字を表すデータを「テキストデータ」と呼び、それ以外を「バイナリデータ」と分類するらしい。
ここまでの話をまとめると。文字コード規格に基づかずに保存されているファイルデータを安全にテキストデータに変換するためのエンコード方式としてbase64がよく利用されているということのようです。
一旦これでbase64に関してはスッキリ✨
じゃあBufferって何?
「余裕を持たせる」という意味で「バッファを作る」ということがあるけど、ITではそれとはニュアンスが異なるらしい。
バッファ(buffer)とは、緩衝材、緩衝装置という意味の英単語で、IT分野では複数の機器などがデータの伝送・処理を行う際に、処理速度や伝送速度の差、タイミングのズレなどを補うためにデータを一時的に蓄えておく記憶装置や記憶領域をこのように呼ぶ。[4]
つまりは一時的にデータを留めておく物のこと。
でも調べていくと、Node.jsのBufferとITで一般的なバッファは意味が異なるっぽい。
LLMの回答
結論から言うと、JavaScriptの Buffer
と IT全般で使われる「バッファ (buffer)」 は関係していますが、意味の広さに違いがあります。
1. IT分野での「バッファ (buffer)」
-
一般的な意味
→ 一時的にデータを蓄えるための領域。- 入出力が遅い装置(ディスク、ネットワーク、GPUなど)とのやり取りでよく使われる。
- 生データの「待機場所」や「中継地点」というニュアンス。
-
例
- ネットワーク受信バッファ
- 音声・映像の再生バッファ(ストリーミング)
- ファイルI/Oのキャッシュバッファ
👉 抽象的な概念で、必ずしも「プログラム上の特定の型やクラス」を指すわけではない。
Buffer
2. JavaScript(Node.js)の -
Node.js が提供する 「生のバイナリデータを扱うためのクラス」。
-
中身は 固定長のバイト配列(
Uint8Array
を継承)。 -
目的
- 文字列とバイナリの相互変換(UTF-8, Base64 など)
- ファイルやソケットの入出力に使うバイト列の操作
-
例
const buf = Buffer.from("hello", "utf-8"); console.log(buf); // <Buffer 68 65 6c 6c 6f>
👉 「IT分野での一般的なバッファ(入出力用の一時データ領域)」を、JavaScript で表現するための具体的な実装クラス。
3. 違いのまとめ
項目 | IT一般の「buffer」 | JavaScript(Node.js)の Buffer
|
---|---|---|
意味 | データを一時的にためる仕組み・概念 | バイナリデータを扱うためのクラス |
抽象度 | 抽象的(物理メモリ領域や仕組み) | 具体的(Uint8Array ベースの実装) |
用途 | I/O処理の中継、速度差の吸収 | ファイル/ネットワーク/文字列⇔バイナリ変換 |
👉 まとめると、
- 「バッファ」 はIT全般で使われる広い概念。
-
Node.js の
Buffer
は、それをJavaScriptの中で安全に扱えるようにした「実装クラス」。
Node.jsのBufferは「バッファ」という概念を実装した「実装クラス」だそうです。
そして、JavaScriptのArrayBufferやUint8Array(TypedArrayと総称するらしい)などもBufferと同じく「バッファ」の実装クラス。ちなみにArrayBuffer > TypedArray > Bufferの順に基底クラスで、ArrayBufferが一番基本的らしいです。[5]
まとめ
- 1と0で構成されたデータのうち、文字コード規格に則ったデータを「テキストデータ」それ以外を「バイナリデータ」と大まかに呼ぶ。
- バイナリデータをテキストデータに変換するためのエンコード方式としてbase64がよく使われる。
- データを一時的に蓄えておく記憶装置や記憶領域をIT分野では「バッファ」という概念で表し、そのJavaScriptでの実装クラスとして
ArrayBuffer
,TypedArray
、Node.jsでの実装クラスとしてBuffer
がある。
// base64文字列をバイナリデータ(Buffer)に戻している
const fileBuffer = Buffer.from(base64Data, "base64");
// BufferをUint8Arrayとして参照するようにしている(型エラーを無視するなら不要)
const uint8Array = new Uint8Array(fileBuffer);
// バイナリデータ(TypedArray)をFileに変換している
return new File([uint8Array], name, { type: contentType });
余談
Buffer
はUint8Array
のサブクラスなのでそのままFileに変換できるが、型エラーが起きるのでUint8Array
に変換してから渡している。
LLMの解説
なるほど、その型エラーは TypeScriptの型定義 が原因です 💡
🔍 なぜエラーになるのか
-
File
コンストラクタの型定義は以下のようになっています(Node.js / DOM 共通):new File(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag)
-
BlobPart
はBuffer
を含まない union 型です:type BlobPart = ArrayBuffer | ArrayBufferView | Blob | string;
-
つまり、TypeScript的には
Buffer
はBlobPart
じゃない扱いになっています。
(実際には Node.js 実装のFile
はBuffer
を受け入れるんですが、型定義が追いついてない)
✅ 解決方法
Uint8Array
に変換する(型的に安全)
方法1: const fileBuffer = Buffer.from(base64Data, "base64");
return new File([new Uint8Array(fileBuffer)], name, { type: contentType });
👉 最初のコードに戻るけど、これは型的には正解。
as unknown as BlobPart
で型キャスト
方法2: const fileBuffer = Buffer.from(base64Data, "base64");
return new File([fileBuffer as unknown as BlobPart], name, { type: contentType });
👉 実行時は問題なく動くので、冗長なコピーを避けたいならこれ。
File
を使わない
方法3: そもそも サーバー側でやることが「アップロード」や「保存」なら File
は不要で、Buffer
だけで十分です。
例: S3 SDK
await s3Client.putObject({
Bucket: "my-bucket",
Key: name,
Body: fileBuffer, // Bufferをそのまま渡せる
ContentType: contentType,
});
🚀 まとめ
- 型エラーの原因は TypeScript の
File
定義がBuffer
を許してないこと。 - 型安全を優先するなら
Uint8Array
に変換 - 効率を優先するなら
as unknown as BlobPart
キャスト - サーバー処理なら
File
自体不要でBuffer
でOK
よくわからないから型エラーの対応をAIに任せたけど、理解すると@ts-expect-error
で黙らせるだけでも良かったと思った。
また一つ経験値を増やすことができた🥳
参考文献
Discussion
むしろ Buffer が node.js 限定の存在では……?(ブラウザサイドだと
ArrayBuffer
,TypedArray
を使うのでコメントありがとうございます!
確かにそうですね
Buffer
はnode.js限定、ArrayBuffer
,TypedArray
はJavaScriptのもの。だけど全て「バッファ」の実装クラスだとわかるように記述しておきます。