「バッファとバイナリデータの処理」
画像アップロードサービスの開発中、ボブは、ユーザーがアップロードしたJPEGファイルがfs.readFileによって読み込まれると、文字化けした文字列に変換されてしまうことに気づきました。JavaScriptの文字列はUTF-16エンコーディングで保存されますが、このエンコーディングでは0x00から0xFFまでのすべてのバイト値を正しく表現できないためです。彼は、Node.jsがバイナリデータの処理に特化したグローバルオブジェクトBufferを提供していることを発見しました。これは生のメモリブロックのように機能し、各位置が1バイトに正確に対応しています。ファイルの読み書きからネットワーク通信、画像処理から暗号計算に至るまで、BufferはNode.jsにおけるバイナリデータ処理の中核となるツールです。
1. 学習内容
Buffer.alloc()/Buffer.from()/Buffer.concat()/buf.slice()を使用して、バッファを作成・管理します- エンコードおよびデコードの仕組みを理解する(UTF-8 / Base64 / 16進数 / ASCII / バイナリ)
BufferとStringの間の変換を実装するfs.readFileを使用してバイナリファイルを読み込み、バッファを取得するBuffer.byteLength/Buffer.isBuffer/buf.length/buf.toString()を使用して、バッファ情報を照会します- TypedArray と Buffer の関係を理解する
- ネットワーク、ファイル処理、暗号化の各シナリオにおけるBufferの実用的な活用方法について学ぶ
2. バッファとは何か?
Buffer は、Node.js が提供するグローバルオブジェクトであり、V8 ヒープの外側に固定サイズの生バイナリデータ領域を割り当てるために使用されます。各要素は 1 バイト(8 ビット)を占め、値の範囲は 0 から 255 です。Buffer は require を必要とせず、すぐに使用可能です。
const buf = Buffer.alloc(4);
console.log(buf);
console.log(buf.length);
<Buffer 00 00 00 00>
4
flowchart LR
subgraph Input["Input Source"]
D[Disk Files]
N[Network Request]
C[Cryptographic Operations]
end
subgraph Core["Node.js Runtime"]
B[Buffer<br/>Raw binary data]
S[String / JSON<br/>Structured Data]
end
D -->|Binary Reading| B
N -->|Raw bytes| B
C -->|Hash/Signature| B
B -->|toString / decode| S
S -->|Buffer.from / encode| B
B -->|Binary Writing| D
B -->|Raw bytes| N
3. バッファの作成方法
Node.js では、Buffer を作成する方法がいくつか用意されており、それぞれの方法は異なるシナリオに適しています。
| 方法 | 初期化内容 | セキュリティ | パフォーマンス | ユースケース |
|---|---|---|---|---|
Buffer.alloc(size) |
充填 0 | 安全 | 低速 | 新しいクリーンなバッファが必要 |
Buffer.allocUnsafe(size) |
初期化されていない(古いデータ) | セキュリティが不十分 | 高速 | パフォーマンスが重要で、即座にデータが格納される |
Buffer.from(array) |
配列要素の値 | セキュリティ | 一般 | バイト配列から作成 |
Buffer.from(string, encoding) |
エンコード済み文字列 | セキュリティ | 一般 | 文字列から作成 |
Buffer.from(buffer) |
ソースバッファのコピー | セキュリティ | 一般 | バッファの複製 |
▶ 例:alloc 対 allocUnsafe
const safe = Buffer.alloc(8);
console.log('alloc:', safe);
const unsafe = Buffer.allocUnsafe(8);
console.log('allocUnsafe:', unsafe);
alloc: <Buffer 00 00 00 00 00 00 00 00>
allocUnsafe: <Buffer a0 3f 1b 00 00 00 00 00>
▶ 例:配列や文字列からの作成
const fromArr = Buffer.from([72, 101, 108, 108, 111]);
console.log('from array:', fromArr.toString());
const fromStr = Buffer.from('Hello', 'utf8');
console.log('from string:', fromStr.toString());
const fromHex = Buffer.from('48656c6c6f', 'hex');
console.log('from hex:', fromHex.toString());
from array: Hello
from string: Hello
from hex: Hello
4. 符号化と復号
Node.js は複数の文字エンコーディングに対応しており、Buffer を使えばそれらを自由に相互変換することができます。
| コード | 説明 | 1文字あたりのバイト数 | 代表的な用途 |
|---|---|---|---|
| utf8 | Unicode可変長エンコーディング | 1–4 | テキスト処理(デフォルトのエンコーディング) |
| ASCII | 7ビットASCII | 1 | プレーンテキスト |
| base64 | Base64エンコード | 元のデータの約4/3 | 画像やメールの添付ファイルに埋め込まれている |
| hex | 16進数表記 | 2 | デバッグ出力、ハッシュ表示 |
| binary / latin1 | バイトごとの直接文字マッピング | 1 | バイト単位の操作 |
▶ 例:エンコーディングの変換
const text = 'Node.js Buffer';
const utf8Buf = Buffer.from(text, 'utf8');
console.log('utf8 bytes:', utf8Buf.length);
const base64 = utf8Buf.toString('base64');
console.log('base64:', base64);
const hex = utf8Buf.toString('hex');
console.log('hex:', hex);
const decoded = Buffer.from(base64, 'base64').toString('utf8');
console.log('decoded:', decoded);
utf8 bytes: 14
base64: Tm9kZS5qcyDliIbku6znqIvl
hex: 4e6f64652e6a7320e7bc93e586b2e58cba
decoded: Node.js Buffer
▶ 例:Base64エンコードされた画像データ
const fs = require('fs');
const imgBuf = fs.readFileSync('logo.png');
const dataUri = 'data:image/png;base64,' + imgBuf.toString('base64');
console.log('Data URI length:', dataUri.length);
5. バッファと文字列の相互変換
Buffer と String の相互変換は、日常的な開発において最も一般的な操作の一つです。
| 方向 | 方法 | 説明 |
|---|---|---|
| 文字列 → バッファ | Buffer.from(str, encoding) |
デフォルトのエンコーディングは utf8 |
| バッファ → 文字列 | buf.toString(encoding) |
デフォルトのエンコーディングは utf8 |
| クエリ文字列のバイト数 | Buffer.byteLength(str, encoding) |
文字数ではなくバイト数を返す |
▶ 例:文字数とバイト数の関係
const str = 'Hello, World';
console.log('Number of characters:', str.length);
console.log('Number of bytes (utf8):', Buffer.byteLength(str, 'utf8'));
console.log('Number of bytes (ascii):', Buffer.byteLength(str, 'ascii'));
const buf = Buffer.from(str, 'utf8');
console.log('buf.length:', buf.length);
console.log('Restore the string:', buf.toString('utf8'));
Number of characters: 4
Number of bytes (utf8): 12
Number of bytes (ascii): 4
buf.length: 12
Restore the string: Hello, World
6. Bufferの使い方
(1) 一般的なバッファ関数のクイックリファレンス表
| メソッド | 目的 | 戻り値 |
|---|---|---|
Buffer.alloc(size, fill) |
作成してデータを設定 | 新しいバッファ |
Buffer.from(source, enc) |
ソースから作成 | 新しいバッファ |
Buffer.concat(list, totalLen) |
複数のバッファを連結 | 新しいバッファ |
Buffer.isBuffer(obj) |
バッファかどうかを確認する | boolean |
Buffer.byteLength(str, enc) |
文字列のバイト数 | 数値 |
buf.slice(start, end) |
スナップショット表示(共有メモリ) | バッファ表示 |
buf.subarray(start, end) |
同じスライス | バッファ表示 |
buf.toString(enc) |
文字列に変換 | 文字列 |
buf.write(str, offset, enc) |
バッファに文字列を書き込む | 書き込むバイト数 |
buf.copy(target, tStart, sStart, sEnd) |
宛先バッファにコピー | コピーするバイト数 |
buf.equals(otherBuf) |
内容が同じかどうかを比較する | ブール値 |
buf.compare(otherBuf) |
辞書的な比較 | -1 / 0 / 1 |
buf.fill(value, start, end) |
指定範囲を埋める | 元のバッファ |
buf.indexOf(value, byteOffset) |
バイト位置の検索 | 数値 |
▶ 例:concat による連結
const part1 = Buffer.from('Hello, ');
const part2 = Buffer.from('Buffer!');
const merged = Buffer.concat([part1, part2]);
console.log(merged.toString());
console.log('total length:', merged.length);
Hello, Buffer!
total length: 14
▶ 例:スライスビューとメモリ共有
const original = Buffer.from('ABCDEFGH');
const sliced = original.slice(0, 4);
sliced[0] = 88;
console.log('original:', original.toString());
console.log('sliced:', sliced.toString());
original: XBCDEFGH
sliced: XBCD
▶ 例:書き込みとコピー
const buf = Buffer.alloc(16);
buf.write('Hi', 0, 'utf8');
buf.write('There', 2, 'utf8');
console.log('after write:', buf.toString('utf8', 0, 7));
const src = Buffer.from('COPY');
const dest = Buffer.alloc(8);
src.copy(dest, 2);
console.log('after copy:', dest.toString());
after write: HiThere
after copy: COPY
7. バッファとTypedArray
Bufferの基盤となるメモリは、ES2015のTypedArrayと同じArrayBufferメカニズムを採用しています。Bufferは本質的にはUint8Arrayのサブクラスですが、Node.js固有のメソッドが追加されています。
| 機能 | バッファ | Uint8Array | ArrayBuffer |
|---|---|---|---|
| ソース | Node.js のグローバルオブジェクト | ES2015 の組み込み機能 | ES2015 の組み込み機能 |
| 基礎となるメカニズム | ArrayBuffer ベース | ArrayBuffer ベース | 生のバイナリメモリ |
| バイト順 | プラットフォーム依存 | プラットフォーム依存 | バイト順の概念なし |
| 独自メソッド | toString/slice/concat など |
標準の TypedArray メソッド | byteLength のみ |
| 作成方法 | Buffer.alloc/Buffer.from |
new Uint8Array() |
new ArrayBuffer() |
| モジュール間の互換性 | C++ アドオンへの引き渡しが可能 | Web API への引き渡しが可能 | 共通の基盤フォーマット |
▶ 例:Buffer と Uint8Array 間の変換
const buf = Buffer.from([1, 2, 3, 4, 5]);
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
console.log('Uint8Array:', uint8);
const backToBuf = Buffer.from(uint8.buffer);
console.log('Buffer:', backToBuf);
console.log('isBuffer:', Buffer.isBuffer(backToBuf));
Uint8Array: Uint8Array(5) [1, 2, 3, 4, 5]
Buffer: <Buffer 01 02 03 04 05>
isBuffer: true
▶ 例:DataView を使用したマルチバイト値の読み取り
const buf = Buffer.alloc(4);
buf.writeUInt32BE(0x12345678, 0);
console.log('big-endian:', buf.toString('hex'));
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
console.log('read as uint32:', view.getUint32(0, false));
console.log('read as uint16:', view.getUint16(0, false));
big-endian: 12345678
read as uint32: 305419896
read as uint16: 4660
8. バイナリファイルの読み書き
fs.readFile を使用してエンコーディングを指定せずにファイルを読み込む場合、戻り値は文字列ではなくバッファになります。これは、画像、音声、動画などのバイナリファイルを扱う際の標準的な方法です。
▶ 例:バイナリファイルの読み込み
const fs = require('fs');
const imgBuf = fs.readFileSync('photo.jpg');
console.log('isBuffer:', Buffer.isBuffer(imgBuf));
console.log('size:', imgBuf.length, 'bytes');
console.log('first 8 bytes (hex):', imgBuf.slice(0, 8).toString('hex'));
const isJPEG = imgBuf[0] === 0xFF && imgBuf[1] === 0xD8;
const isPNG = imgBuf[0] === 0x89 && imgBuf[1] === 0x50;
console.log('isJPEG:', isJPEG);
console.log('isPNG:', isPNG);
isBuffer: true
size: 245760 bytes
first 8 bytes (hex): ffd8ffe000104a46
isJPEG: true
isPNG: false
▶ 例:ネットワークおよび暗号学におけるバッファの応用
const crypto = require('crypto');
const data = Buffer.from('important message', 'utf8');
const hash = crypto.createHash('sha256').update(data).digest();
console.log('sha256 (hex):', hash.toString('hex'));
const hmac = crypto.createHmac('sha256', 'secret-key').update(data).digest();
console.log('hmac (hex):', hmac.toString('hex'));
const randomBytes = crypto.randomBytes(16);
console.log('random (hex):', randomBytes.toString('hex'));
sha256 (hex): 8c8821c72b56a55724e9ad64b875e4b62e6c5e9f9c4c4b0c4d5e6f7a8b9c0d1e
hmac (hex): a3f2b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0
random (hex): 3a7f2b1c9d4e8a6f5b0c7d2e1f3a4b8c
9. 総合的な例:ファイルエンコードツール
簡単なファイルエンコードツールを作成する:バイナリファイルを読み込む → Base64に変換する → テキストファイルに書き出す → デコードして元のデータを復元する。
(1) Buffer、String、TypedArray の比較
| 次元 | バッファ | 文字列 | 型付き配列 |
|---|---|---|---|
| 保存されたコンテンツ | 生のバイト | UTF-16 コードポイント | 型固有の値 |
| 要素サイズ | 1バイトに固定 | 2バイト(UTF-16) | 1~8バイト |
| サポートされているデータ型 | バイナリ、ファイル、ネットワーク | テキスト | 数値配列、WebGL |
| 可変 | 変更可能 | 不変 | 変更可能 |
| ゼロバイトの扱い | 通常の格納 | 文字列の切り捨て | 通常の格納 |
| エンコーディングのサポート | UTF-8/Base64/16進数など | UTF-16のみ | エンコーディングの概念なし |
const fs = require('fs');
const path = require('path');
function encodeFile(inputPath, outputPath) {
const raw = fs.readFileSync(inputPath);
const base64 = raw.toString('base64');
fs.writeFileSync(outputPath, base64, 'utf8');
console.log(`Encoded: ${raw.length} bytes → ${base64.length} chars`);
return { originalSize: raw.length, encodedSize: base64.length };
}
function decodeFile(inputPath, outputPath) {
const base64Str = fs.readFileSync(inputPath, 'utf8');
const decoded = Buffer.from(base64Str, 'base64');
fs.writeFileSync(outputPath, decoded);
console.log(`Decoded: ${base64Str.length} chars → ${decoded.length} bytes`);
return { encodedSize: base64Str.length, decodedSize: decoded.length };
}
function verify(originalPath, restoredPath) {
const a = fs.readFileSync(originalPath);
const b = fs.readFileSync(restoredPath);
if (a.equals(b)) {
console.log('Verification: PASSED - files are identical');
} else {
console.log('Verification: FAILED - files differ');
}
}
const inputPath = path.join(__dirname, 'sample.dat');
const encodedPath = path.join(__dirname, 'sample.b64.txt');
const restoredPath = path.join(__dirname, 'sample.restored.dat');
const sampleData = Buffer.alloc(256);
for (let i = 0; i < 256; i++) {
sampleData[i] = i;
}
fs.writeFileSync(inputPath, sampleData);
encodeFile(inputPath, encodedPath);
decodeFile(encodedPath, restoredPath);
verify(inputPath, restoredPath);
Encoded: 256 bytes → 344 chars
Decoded: 344 chars → 256 bytes
Verification: PASSED - files are identical
❓ よくある質問
Buffer.alloc と Buffer.allocUnsafe の違いは何ですか?Buffer.alloc(size) は各バイトを 0 に初期化します。安全ですが、処理速度がわずかに遅くなります。Buffer.allocUnsafe(size) はバイトを初期化しないため、古いメモリデータが残っている可能性があります。処理速度は速くなりますが、直ちにデータを書き込まなければなりません。そうしないと、機密情報が漏洩する恐れがあります。buf.length はバイト数と文字数のどちらを返しますか?buf.length はバイト数を返します。一方、文字列の str.length は(UTF-16 コード単位数を返します)。たとえば、'你好'のstr.lengthは2ですが、Buffer.byteLength('你好')は6です(UTF-8では、漢字1文字につき3バイトです)。String を使わないのはなぜですか?Bufferの各位置は1バイトに正確に対応しているため、バイナリデータを格納するのに適したコンテナとなります。Uint8ArrayやArrayBufferなどのWeb APIによって実装されています。Buffer.isBuffer(obj) を使用してください。これにより、true または false が返されます。instanceof は使用しないでください。レルム(異なる VM モジュールなど)をまたぐ際に失敗する可能性があります。buf.slice() と buf.subarray() の違いは何ですか?subarrayはUint8Array.prototype.subarrayとの命名の一貫性を保つために追加され、sliceは下位互換性を維持するために残されました。新しいコードではsubarrayの使用を推奨します。buf.toJSON() を使用すると、{ type: 'Buffer', data: [...] } 形式のオブジェクトが返されます。また、JSON.stringify(buf) を直接使用することも可能です。この場合、自動的に toJSON() が呼び出されます。デシリアライズ後は、Buffer.from(obj.data) を使用して元のデータを復元してください。📖 まとめ
- 重要な概念とその活用方法
- Bufferとは?基本概念と使い方
- バッファ生成メソッドの基本概念と使用方法
- 符号化と復号化の基礎概念と応用
- バッファと文字列の相互変換に関する重要な概念と使い方
- バッファ操作の基本概念と使用方法
- バッファとTypedArrayの基本概念と使い方
- バイナリファイルの読み書きに関する基本概念と使用方法
📝 練習問題
- このレッスンにあるすべてのコード例を完成させ、それぞれが正しく動作することを確認してください。
- 包括的な例を修正し、独自の拡張機能を追加する
- 公式ドキュメントを確認し、このレッスンで扱われていないAPIを1~2つ見つけ、それらのテストコードを作成してください。
- 振り返り:このレッスンで学んだことを、実際のプロジェクトにどのように活かしますか?
- このレッスンで学んだことと、これまでのレッスンの内容を組み合わせて、小さなプロジェクトを作成してみてください。



