バイナリの深淵:BinaryReader/Writerによる超高速・機密独自フォーマットの極限実装
レガシーシステムのブラックボックスを暴き、数百万行のトランザクションをコンマ数秒で飲み込む。そのとき、我々が手にする武器はCSVでもJSONでもない。無機質なバイトの配列、すなわち「バイナリ」である。
テキストベースのフォーマットは人間には優しいが、マシンにとっては重荷だ。パース処理のオーバーヘッド、数値の文字列変換、そして肥大化したファイルサイズ。システム間連携や秘匿性の高いデータの局所保存において、`BinaryReader`と`BinaryWriter`を用いた独自バイナリフォーマットの設計は、今なおシニアエンジニアにとって必須のカードである。
本稿では、VB.NETの底力を引き出し、メモリの1バイトすら無駄にしない極限のシリアライズ・デシリアライズ実装を解説する。
—
1. 独自バイナリフォーマット設計の鉄則
バイナリファイルを扱う際、最大の悪夢は「エンディアンの不一致」と「構造体のパディング(アライメント)」、そして「バージョンアップによる破綻」である。
特にVB.NET(.NET環境)で動作する場合、マネージドヒープのメモリレイアウトはCLR(共通言語ランタイム)の最適化に委ねられるため、C/C++の `struct` のように単純にメモリをファイルへ直書きしてはならない。必ずストリームを介して、明示的なバイトオーダー(通常はリトルエンディアン)でシリアライズ・デシリアライズを行うべきだ。
堅牢なバイナリファイルの構造設計
1. マジックナンバー (Magic Number): ファイルの先頭4バイト等に、これが自社フォーマットであることを示す識別子(例: `0x56534231` = “VSB1″)を置く。
2. バージョンヘッダー (Version): 将来の仕様変更に備え、フォーマットのバージョンを必ず含める。
3. データ長/レコード数 (Count): 読み込み時にループ回数やバッファサイズを静的に確保するため、可変長データの前に必ず件数を置く。
4. ペイロード (Payload): プリミティブ型およびパッキングされた固定長/可変長データ。
—
2. 実装:超高速シリアライザー・デシリアライザー
以下のコードは、独自の機密データを効率的にファイルへ書き出し、ミリ秒単位で読み戻すためのVB.NETによる実装例である。
`Using`構文を徹底し、アンマネージド・リソースに近いストリームのライフサイクルを完全に掌握する。
Option Strict On
Option Explicit On
Imports System.IO
Imports System.Text
Namespace SystemArchitecture.BinaryOperations
‘
‘
Public Structure SecureRecord
Public Id As Integer
Public Timestamp As Long
Public SecurityLevel As Byte
Public Payload As String
End Structure
‘
‘
Public NotInheritable Class BinaryEngine
‘ マジックナンバー: “VBR1″ (Visual Basic Binary Reader 1)
Private Shared ReadOnly MagicNumber As Integer = &H31524256
Private Const CurrentVersion As UShort = 1
”’
”’
Public Shared Sub WriteBinaryFile(filePath As String, records As List(Of SecureRecord))
‘ FileStreamのバッファサイズを64KBに明示的指定し、ディスクI/Oのボトルネックを粉砕する
Using fs As New FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 65536, FileOptions.SequentialScan)
Using writer As New BinaryWriter(fs, Encoding.UTF8, leaveOpen:=False)
‘ 1. ヘッダー情報の書き込み
writer.Write(MagicNumber)
writer.Write(CurrentVersion)
‘ 2. レコード数の書き込み
Dim count As Integer = If(records IsNot Nothing, records.Count, 0)
writer.Write(count)
If count = 0 Then Return
‘ 3. ペイロードの書き込み
For Each rec In records
writer.Write(rec.Id)
writer.Write(rec.Timestamp)
writer.Write(rec.SecurityLevel)
‘ 文字列は可変長のため、Length + バイト配列として書き込む
writer.Write(rec.Payload)
Next
writer.Flush()
End Using
End Using
End Sub
”’
”’
Public Shared Function ReadBinaryFile(filePath As String) As List(Of SecureRecord)
Dim results As New List(Of SecureRecord)()
If Not File.Exists(filePath) Then
Throw New FileNotFoundException(“指定されたバイナリファイルが存在しません。”, filePath)
End If
Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 65536, FileOptions.SequentialScan)
Using reader As New BinaryReader(fs, Encoding.UTF8, leaveOpen:=False)
‘ 1. マジックナンバーの検証(不正ファイルの即座な弾き落とし)
Dim magic As Integer = reader.ReadInt32()
If magic <> MagicNumber Then
Throw New InvalidDataException(“ファイルの整合性エラー: マジックナンバーが一致しません。”)
End If
‘ 2. バージョン検証
Dim version As UShort = reader.ReadUInt16()
If version > CurrentVersion Then
Throw New NotSupportedException($”サポートされていないファイルバージョンです (Version: {version})”)
End If
‘ 3. レコード数の取得
Dim count As Integer = reader.ReadInt32()
results.Capacity = count ‘ メモリの再割り当て(Reallocation)を防止し、パフォーマンスを極限まで高める
‘ 4. ペイロードの読み込み
For i As Integer = 0 To count – 1
Dim rec As New SecureRecord With {
.Id = reader.ReadInt32(),
.Timestamp = reader.ReadInt64(),
.SecurityLevel = reader.ReadByte(),
.Payload = reader.ReadString()
}
results.Add(rec)
Next
End Using
End Using
Return results
End Function
End Class
End Namespace
—
3. チーフアーキテクトが解説する「極限最適化」の要点
上記のコードには、単なる「動くコード」を超えた、プロダクション環境で生き残るための設計思想が凝縮されている。
① FileStreamのバッファリングとファイルオプション
デフォルトのコンストラクタで`FileStream`を生成すると、バッファサイズが小さく、ディスクアクセスが頻発する。ここでは64KB(65536バイト)のバッファを手動指定し、さらに `FileOptions.SequentialScan` を渡すことで、OSのファイルキャッシュに対して「シーケンシャルアクセスである」というヒントを与えている。これにより、OSレベルの先読み最適化が強制有効化される。
② `List(Of T).Capacity` の事前確保によるメモリ最適化
デシリアライズ時に、ファイルから読み込んだレコード数 (`count`) をあらかじめ `Capacity` に設定している。
これを行わないと、要素が追加されるたびに内部配列のサイズ変更(メモリ確保とガベージコレクターへの負荷増大)が連鎖的に発生する。数百万件規模のデータを扱うシステムにおいて、この1行の有無が生死を分ける。
③ マジックナンバーによるフェイルファスト (Fail-Fast)
不正なファイルや破損したファイルを読み込んだ際、メモリーリークや予期せぬ例外の伝播を防ぐため、先頭4バイトで即座に弾き落とす。レガシーシステム連携において「怪しいデータは一瞬で例外を吐いて止まる」ことは、最大のセキュリティ対策であり、障害解析の容易さに直結する。
—
4. レガシー環境・相互運用への応用
このバイナリフォーマットは、VB.NETだけでなく、C++やC#、さらにはWin32 APIを直接叩くレガシーなVBA(Excelマクロ)環境とのデータ連携にも応用可能だ。
VBA側でバイナリを読み込む際は、`Open Binary As #1` を用い、`Get #1,, id` のようにプリミティブ型を正確にパッキングしてマッピングすることで、超高速なクロスプラットフォーム連携基盤が完成する。
テキスト解析の呪縛から解放されよ。バイトの海を直接泳ぎ切るコードこそが、真にシステムを支配するエンジニアの武器なのだ。
