巨大CSVを「メモリの制約」という檻から解き放つ:VBScriptによる動的分割パースの極意
数十万行に及ぶCSVを前にして、安易に`ReadAll`や配列への一括ロードを試みていないか?もしそうなら、そのスクリプトは巨大なデータという「爆弾」を抱えて爆発を待っているに等しい。
VBScriptはレガシーではない。メモリ管理とオブジェクトのライフサイクルを制御すれば、依然としてWindows環境における最強の軽量データ処理エンジンだ。今日は、FSO(FileSystemObject)を駆使し、メモリ負荷を最小限に抑えながら数ギガのCSVをキーごとに動的に分割する、実戦的なアーキテクチャを伝授する。
—
1. 勘違いを捨てる:なぜ「一括読込」は悪なのか
VBScriptの実行エンジンは、巨大な文字列をメモリ上に展開すると急速にパフォーマンスが劣化する。ガベージコレクションの挙動は決してスマートとは言えないからだ。
ここで目指すべきは「ストリーム・プロセス」だ。
1行読み込み、キーを判定し、該当するファイルへ追記し、メモリから破棄する。このサイクルを徹底するだけで、メモリ消費量は常に一定(数KBオーダー)に収まる。
2. 実装:動的ファイル生成とストリーム制御
以下のコードは、CSVの第1列(支社コード)をキーとして、動的に出力先ファイルを生成・保持する実装だ。重要なのは、「開いたファイルオブジェクトを辞書(Dictionary)で保持し、必要に応じて閉じる」というライフサイクル管理である。
Option Explicit
‘ メモリ消費を最小化する動的分割エンジン
Sub SplitLargeCSV(strSourcePath, strOutputDir)
Dim fso, tsIn, dictOutputs
Dim line, cols, key, outPath
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set dictOutputs = CreateObject(“Scripting.Dictionary”)
‘ 入力ファイルをストリームとして開く(一括読込厳禁)
Set tsIn = fso.OpenTextFile(strSourcePath, 1) ‘ 1 = ForReading
Do Until tsIn.AtEndOfStream
line = tsIn.ReadLine
If Len(line) > 0 Then
cols = Split(line, “,”)
key = cols(0) ‘ 第1列をキーとする
‘ 初めて出現するキーならファイルを作成
If Not dictOutputs.Exists(key) Then
outPath = fso.BuildPath(strOutputDir, key & “.csv”)
‘ OpenTextFileで追記モードとして保持
dictOutputs.Add key, fso.OpenTextFile(outPath, 8, True)
End If
‘ 該当するストリームに書き込み
dictOutputs(key).WriteLine line
End If
Loop
‘ ライフサイクルの終了処理(ここが重要)
tsIn.Close
Dim k
For Each k In dictOutputs.Keys
dictOutputs(k).Close
Next
‘ 明示的なオブジェクト解放
Set dictOutputs = Nothing
Set tsIn = Nothing
Set fso = Nothing
End Sub
3. シニアエンジニアが意識すべき「極限の最適化」
① 辞書オブジェクト(Dictionary)の活用
`dictOutputs`にファイルハンドル(TextStream)を保持させている点に注目してほしい。これにより、毎回ファイルをOpen/Closeするオーバーヘッドを排除している。OSに対するI/O要求を最小化することは、ディスクI/Oのボトルネックを解消する最も有効な手段だ。
② I/Oバッファの意識
VBScriptのFSOは、バックグラウンドでシステムキャッシュを使用する。もし処理速度が極端に遅いと感じる場合、それは物理ディスクの書き込み待ち(Flush)が発生しているからだ。Windows APIの`FlushFileBuffers`を呼び出したい衝動に駆られるかもしれないが、VBScript単体では難しい。その場合は、`ADODB.Stream`オブジェクトに切り替えることで、バイナリレベルでのバッファ制御が可能になる。
③ 巨大なCSVの境界条件
- ヘッダー行の扱い: 上記コードはヘッダーを無視している。実務では`firstLine = tsIn.ReadLine`で退避させ、各ファイル生成時に先頭行として書き込むロジックを追加せよ。
- 文字コードの罠: `OpenTextFile`はデフォルトでシステムコードページ(Shift-JIS等)を使う。UTF-8で保存された巨大CSVを扱う場合は、必ず`ADODB.Stream`を使い、`Charset`プロパティを明示的に指定すること。さもなくば、マルチバイト文字が文字化けの海に沈むことになる。
4. 最後に:伝説のアーキテクトからの助言
VBScriptは「古い」のではない。「完成されている」のだ。
メモリを食い荒らすモダンなフレームワークに頼る前に、OSの根幹に触れるようなストリーム処理を一度書いてみるがいい。
このコードは、数万行のログ解析だろうと、数百万行のトランザクションデータだろうと、淡々と、かつ確実に処理を完遂する。エラーハンドリング(`On Error Resume Next`の適切な使用と、ファイルロックの検知)さえ実装すれば、これは貴方の職場で数十年動き続ける不滅のツールとなるはずだ。
コードは嘘をつかない。メモリのライフサイクルを支配する者が、システムを制するのだ。
