【実務・中級編】【メモリ削減】AtEndOfStreamを活用した大容量テキストファイルの1行ずつ高速読み込み処理 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【メモリ削減】AtEndOfStreamを活用した大容量テキストファイルの1行ずつ高速読み込み処理

開発現場でよくある悪夢の一つが、数GB規模の巨大ログファイル処理による「メモリ不足(Out of Memory)」エラーだ。
「とりあえず `ReadAll` で一括読み込みして、`Split` で配列にバラせばいいか」――そんな安易な実装が、プロダクション環境でサーバーをハングアップさせ、夜間バッチを沈没させる。

VBScriptのFileSystemObject(FSO)におけるメモリ管理のメカニズムを理解していれば、このような惨事は完全に防ぐことができる。今回は、`AtEndOfStream` を駆使したストリーミング処理により、極小のメモリフットプリントで大容量ファイルを爆速かつ安全に処理する極限の設計パターンを伝授する。

—

なぜ `ReadAll` や `Read` の一括読み込みは実務で破綻するのか?

多くのプログラマが陥る罠が、`TextStream` オブジェクトの `.ReadAll()` メソッドの安易な使用だ。

‘ 【アンチパターン】絶対にやってはいけない実装
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set ts = fso.OpenTextFile(“C:\Logs\huge_server.log”, 1)

‘ ファイル全体を一気にメモリ上にロードする
allText = ts.ReadAll
ts.Close

‘ 文字列の配列に分割(ここでさらにメモリが倍増する)
lines = Split(allText, vbCrLf)
For i = 0 To UBound(lines)
‘ 処理…
Next

致命的な問題点

1. メモリの爆発的消費: VBScriptの内部文字列はBSTR(Basic String)として管理され、UTF-16で保持される。数GBのテキストファイルを `ReadAll` すると、その数倍の物理メモリ(および仮想メモリ)がプロセスに要求される。
2. ガベージコレクションの限界: VBScriptのCOMコンポーネントにおけるメモリ解放は厳密ではなく、巨大なオブジェクトが破棄されるタイミングでメモリフラグメンテーション(断片化)を引き起こす。

この問題を根本から解決するのが、「必要な分だけ読み込み、即座に処理し、メモリに残さない」ストリーミング・アーキテクチャである。

—

堅牢なストリーミング処理の設計:`AtEndOfStream` ループ

FSOの `TextStream` が持つ `AtEndOfStream` プロパティと `.ReadLine()` メソッドを組み合わせることで、ファイルサイズに依存しない一定のメモリ消費量(O(1)の空間計算量)での処理が可能になる。

実務で必須となるエラーハンドリングとリソース管理

業務自動化スクリプトにおいて、ファイルロックや予期せぬI/Oエラーはつきものだ。以下の設計要件を満たすプロダクションコードを構築する。

  • 排他制御とエラーハンドリング: `On Error Resume Next` を局所的に使い、ファイルアクセスの競合をハンドリングする。
  • 確実なオブジェクト解放: 例外が発生しても、必ず `TextStream` と `FileSystemObject` のインスタンスを解放し、ファイルロックを残さない。
  • 文字コードの考慮: Windows環境のログに多いShift-JIS(CP932)やUTF-8への対応。

—

【コピペOK】プロダクションコード例

以下に、実務の現場でそのまま組み込める堅牢なテンプレートを示す。このスクリプトは、大容量ログファイルから特定キーワードを検出し、別ファイルへ高速に抽出(フィルタリング)する処理を想定している。

Option Explicit

‘ 実行のエントリーポイント
Sub ProcessLargeLogFile()
Dim fso, targetPath, outputPath
Dim sourceStream, destStream
Dim lineCount, matchCount
Dim searchKeyword

targetPath = “C:\Logs\production_giant.log”
outputPath = “C:\Logs\filtered_result.log”
searchKeyword = “ERROR”

WScript.Echo “処理を開始します: ” & Now

Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ 1. ファイルが存在するかチェック
If Not fso.FileExists(targetPath) Then
WScript.Echo “エラー: 対象ファイルが見つかりません – ” & targetPath
Exit Sub
End If

‘ 2. 読み込み専用・共有モードでテキストストリームを開く
‘ 第2引数: 1 (ForReading), 第3引数: False (作成しない), 第4引数: 0 (ASCII/SJIS ※必要に応じて変更)
Set sourceStream = fso.OpenTextFile(targetPath, 1, False, 0)

‘ 出力用ストリームの作成(上書き許可、Unicode指定なしの場合はASCII)
Set destStream = fso.CreateTextFile(outputPath, True, False)

lineCount = 0
matchCount = 0

‘ 3. 【核心】AtEndOfStreamを用いたストリーミングループ
‘ ファイルポインタが終端に達するまで、1行ずつメモリに読み込む
Do While Not sourceStream.AtEndOfStream
Dim currentLine
currentLine = sourceStream.ReadLine
lineCount = lineCount + 1

‘ 4. 1行ごとのビジネスロジック(例:キーワード検索と抽出)
If InStr(1, currentLine, searchKeyword, vbTextCompare) > 0 Then
destStream.WriteLine currentLine
matchCount = matchCount + 1
End If

‘ ※数百万行を超える処理の場合、進捗をコンソールに出力する配慮があると親切
If lineCount Mod 100000 = 0 Then
WScript.Echo lineCount & ” 行処理完了…”
End If
Loop

‘ 5. ストリーミングの終了とリソースの確実な解放
sourceStream.Close
destStream.Close

Set sourceStream = Nothing
Set destStream = Nothing
Set fso = Nothing

WScript.Echo “処理が正常終了しました。”
WScript.Echo “総行数: ” & lineCount & “行 / 該当行数: ” & matchCount & “行”
WScript.Echo “出力先: ” & outputPath
End Sub

‘ 処理の実行
ProcessLargeLogFile()

—

現場のエンジニアへ:さらなるパフォーマンスを引き出す知見

この実装パターンをベースに、さらに実務レベルでのチューニングを行うための知見を共有する。

1. 文字コード(UTF-8 / Shift-JIS)の罠

FSOの `OpenTextFile` の第4引数(Format)は癖がある。

  • `0` (TristateFalse): システムのデフォルト(通常はShift-JIS / CP932)
  • `-1` (TristateTrue): Unicode (UTF-16LE)
  • `-2` (TristateUseDefault): システムの設定

現代のシステムから出力されるUTF-8(BOMなし)のログファイルをFSOで直接読み込もうとすると、文字化けを起こすか正しく終端判定できない場合がある。
もし対象がUTF-8ファイルである場合は、FSOの限界を超えるため、ADO Streamオブジェクト(`ADODB.Stream`)を代替として採用し、同様に `EOS` (End of Stream) プロパティを使ったストリーミングを行うべきだ。

2. データベースや外部API連携への応用

この1行ずつ読み込むループ構造の最大の強者は、「読み込んだデータを即座に外部リソースへ投げる」というパイプライン処理が組める点にある。

‘ 例:読み込んだログをその場でDB(ADO)にバッチINSERTしていく構造
Do While Not sourceStream.AtEndOfStream
currentLine = sourceStream.ReadLine
‘ データベースへトランザクションを張ってインサート
‘ conn.Execute “INSERT INTO LogTable … ”
Loop

このように、メモリ上にデータを蓄積せず「流す(Stream)」設計を徹底することで、VBScriptという軽量なスクリプト言語であっても、エンタープライズレベルの大規模データ処理に耐えうる堅牢なツールを構築できる。

「動けばいい」のコードから脱却し、メモリとリソースを支配するプロフェッショナルな自動化スクリプトを設計してほしい。

タイトルとURLをコピーしました