404 Not Found

404 Not Found


nginx

「バッファとバイナリデータの処理」

画像アップロードサービスの開発中、ボブは、ユーザーがアップロードしたJPEGファイルがfs.readFileによって読み込まれると、文字化けした文字列に変換されてしまうことに気づきました。JavaScriptの文字列はUTF-16エンコーディングで保存されますが、このエンコーディングでは0x00から0xFFまでのすべてのバイト値を正しく表現できないためです。彼は、Node.jsがバイナリデータの処理に特化したグローバルオブジェクトBufferを提供していることを発見しました。これは生のメモリブロックのように機能し、各位置が1バイトに正確に対応しています。ファイルの読み書きからネットワーク通信、画像処理から暗号計算に至るまで、BufferはNode.jsにおけるバイナリデータ処理の中核となるツールです。

1. 学習内容


2. バッファとは何か?

Buffer は、Node.js が提供するグローバルオブジェクトであり、V8 ヒープの外側に固定サイズの生バイナリデータ領域を割り当てるために使用されます。各要素は 1 バイト(8 ビット)を占め、値の範囲は 0 から 255 です。Bufferrequire を必要とせず、すぐに使用可能です。

JAVASCRIPT
const buf = Buffer.alloc(4);
console.log(buf);
console.log(buf.length);
TEXT
<Buffer 00 00 00 00>
4
100%
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

JAVASCRIPT
const safe = Buffer.alloc(8);
console.log('alloc:', safe);

const unsafe = Buffer.allocUnsafe(8);
console.log('allocUnsafe:', unsafe);
TEXT
alloc: <Buffer 00 00 00 00 00 00 00 00>
allocUnsafe: <Buffer a0 3f 1b 00 00 00 00 00>

▶ 例:配列や文字列からの作成

JAVASCRIPT
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());
TEXT
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 バイト単位の操作

▶ 例:エンコーディングの変換

JAVASCRIPT
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);
TEXT
utf8 bytes: 14
base64: Tm9kZS5qcyDliIbku6znqIvl
hex: 4e6f64652e6a7320e7bc93e586b2e58cba
decoded: Node.js Buffer

▶ 例:Base64エンコードされた画像データ

JAVASCRIPT
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. バッファと文字列の相互変換

BufferString の相互変換は、日常的な開発において最も一般的な操作の一つです。

方向 方法 説明
文字列 → バッファ Buffer.from(str, encoding) デフォルトのエンコーディングは utf8
バッファ → 文字列 buf.toString(encoding) デフォルトのエンコーディングは utf8
クエリ文字列のバイト数 Buffer.byteLength(str, encoding) 文字数ではなくバイト数を返す

▶ 例:文字数とバイト数の関係

JAVASCRIPT
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'));
TEXT
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 による連結

JAVASCRIPT
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);
TEXT
Hello, Buffer!
total length: 14

▶ 例:スライスビューとメモリ共有

JAVASCRIPT
const original = Buffer.from('ABCDEFGH');
const sliced = original.slice(0, 4);

sliced[0] = 88;
console.log('original:', original.toString());
console.log('sliced:', sliced.toString());
TEXT
original: XBCDEFGH
sliced: XBCD

▶ 例:書き込みとコピー

JAVASCRIPT
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());
TEXT
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 間の変換

JAVASCRIPT
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));
TEXT
Uint8Array: Uint8Array(5) [1, 2, 3, 4, 5]
Buffer: <Buffer 01 02 03 04 05>
isBuffer: true

▶ 例:DataView を使用したマルチバイト値の読み取り

JAVASCRIPT
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));
TEXT
big-endian: 12345678
read as uint32: 305419896
read as uint16: 4660

8. バイナリファイルの読み書き

fs.readFile を使用してエンコーディングを指定せずにファイルを読み込む場合、戻り値は文字列ではなくバッファになります。これは、画像、音声、動画などのバイナリファイルを扱う際の標準的な方法です。

▶ 例:バイナリファイルの読み込み

JAVASCRIPT
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);
TEXT
isBuffer: true
size: 245760 bytes
first 8 bytes (hex): ffd8ffe000104a46
isJPEG: true
isPNG: false

▶ 例:ネットワークおよび暗号学におけるバッファの応用

JAVASCRIPT
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'));
TEXT
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のみ エンコーディングの概念なし
JAVASCRIPT
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);
TEXT
Encoded: 256 bytes → 344 chars
Decoded: 344 chars → 256 bytes
Verification: PASSED - files are identical

❓ よくある質問

Q Buffer.allocBuffer.allocUnsafe の違いは何ですか?
A Buffer.alloc(size) は各バイトを 0 に初期化します。安全ですが、処理速度がわずかに遅くなります。Buffer.allocUnsafe(size) はバイトを初期化しないため、古いメモリデータが残っている可能性があります。処理速度は速くなりますが、直ちにデータを書き込まなければなりません。そうしないと、機密情報が漏洩する恐れがあります。
Q buf.length はバイト数と文字数のどちらを返しますか?
A buf.length はバイト数を返します。一方、文字列の str.length は(UTF-16 コード単位数を返します)。たとえば、'你好'str.lengthは2ですが、Buffer.byteLength('你好')は6です(UTF-8では、漢字1文字につき3バイトです)。
Q バイナリデータの処理に String を使わないのはなぜですか?
A JavaScript の文字列は UTF-16 エンコーディングを使用していますが、このエンコーディングでは 0x00 バイトを正しく表現できず(切り捨てられてしまいます)、またマルチバイト文字とバイトの間に直接的な対応関係がありません。Bufferの各位置は1バイトに正確に対応しているため、バイナリデータを格納するのに適したコンテナとなります。
Q BufferはJavaScriptの一部ですか?
A いいえ。BufferはNode.js固有のグローバルオブジェクトであり、ECMAScript仕様の一部ではありません。ブラウザ環境にはBufferは存在せず、代わりにUint8ArrayArrayBufferなどのWeb APIによって実装されています。
Q ある値がバッファであるかどうかをどのように判断すればよいですか?
A Buffer.isBuffer(obj) を使用してください。これにより、true または false が返されます。instanceof は使用しないでください。レルム(異なる VM モジュールなど)をまたぐ際に失敗する可能性があります。
Q buf.slice()buf.subarray() の違いは何ですか?
A Node.js では、両者の動作は同一です。どちらも、基盤となるメモリを共有するビューを返します。subarrayUint8Array.prototype.subarrayとの命名の一貫性を保つために追加され、sliceは下位互換性を維持するために残されました。新しいコードではsubarrayの使用を推奨します。
Q Bufferを安全にJSONに変換するにはどうすればよいですか?
A buf.toJSON() を使用すると、{ type: 'Buffer', data: [...] } 形式のオブジェクトが返されます。また、JSON.stringify(buf) を直接使用することも可能です。この場合、自動的に toJSON() が呼び出されます。デシリアライズ後は、Buffer.from(obj.data) を使用して元のデータを復元してください。

📖 まとめ


📝 練習問題

  1. このレッスンにあるすべてのコード例を完成させ、それぞれが正しく動作することを確認してください。
  2. 包括的な例を修正し、独自の拡張機能を追加する
  3. 公式ドキュメントを確認し、このレッスンで扱われていないAPIを1~2つ見つけ、それらのテストコードを作成してください。
  4. 振り返り:このレッスンで学んだことを、実際のプロジェクトにどのように活かしますか?
  5. このレッスンで学んだことと、これまでのレッスンの内容を組み合わせて、小さなプロジェクトを作成してみてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%