こんにちは!VBScriptの世界へようこそ。
業務自動化の現場で、エクセルやテキストファイルの処理を任されると、避けて通れないのがFileSystemObject(通称:FSO)ですね。
「数メガバイト程度のテキストファイルなら `OpenTextFile` で一発だぜ!」
――そう思って油断していると、ある日突然、業務用の巨大ログファイルや、改行のない変態的なCSV(数100MB〜数GB)を読み込ませた瞬間に、VBScriptが「メモリ不足です (Out of Memory)」という冷酷なエラーを吐いて盛大にクラッシュする。……エンジニアの皆様なら、一度や二度はこの悪夢を経験したことがあるのではないでしょうか。
今回は、そんな修羅場をくぐり抜けてきたあなたへ贈る、【上級プロフェッショナル向け】FSO TextStreamにおけるメモリ上限(Out of Memory)を回避するブロック境界パースアルゴリズムを授けましょう。
ここをクリアすれば、あなたはもう「なんとなく動かしているスクリプト職人」ではありません。VBScriptのメモリとI/Oの限界を完全に支配する、真の自動化アーキテクトです。さあ、一緒に深淵を覗いてみましょう!
—
なぜ巨大ファイルで「Out of Memory」が起きるのか?
まずは敵を知ることから始めましょう。
VBScriptでテキストファイルを読み込む際、私たちはよく次のようなコードを書きます。
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set ts = fso.OpenTextFile(“huge_file.txt”, 1)
allText = ts.ReadAll ‘ ← ここで死亡
`ReadAll` や、改行区切りを無視して巨大な1行が続くデータを `ReadLine` でメモリ上に展開しようとすると、VBScriptの裏側にあるCOM(Component Object Model)の文字列バッファが限界を迎えます。特にVBScriptの文字列はBSTR(Basic String)として管理されており、巨大なデータを一気にメモリへ載せようとすると、ヒープ領域が圧迫されて一巻の終わりです。
解決の鍵:ストリームの「切り取り読み(ブロック読み込み)」
OSのファイルシステムは、実は細切れのブロック単位でデータを管理しています。
VBScriptの `TextStream` オブジェクトには、ファイルを丸ごと読むのではなく、「指定したバイト数だけ読み込む(Read)」というメソッドが存在します。
これを利用して、「メモリが安全に耐えられるサイズ(例:数MBごと)に区切り、改行コードの境界を自前で計算して安全にパース・処理していく」のが、今回紹介するブロック境界パースアルゴリズムです。
—
実装コード:メモリを枯渇させないブロックパーサー
開発現場でそのままコピペして、すぐに検証・流用できる実用的なVBScriptコードを用意しました。
ここでは、巨大なファイルを一定のバイト数(チャンク)ごとに読み込み、メモリを節約しながら安全に処理する仕組みを実装しています。
Option Explicit
‘ ==============================================================================
‘ 【極限知見】FSO TextStream ブロック境界パースアルゴリズム
‘ 巨大ファイルや改行のない超長行データをメモリ枯渇させずに安全処理する
‘ ==============================================================================
Sub ParseHugeFileSafely(filePath, chunkSize)
Dim fso, fileObj, ts
Dim buffer, carryOver, readBytes
Dim targetPos, lineStr
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ ファイルが存在するかチェック
If Not fso.FileExists(filePath) Then
WScript.Echo “エラー: 指定されたファイルが見つかりません -> ” & filePath
Exit Sub
End If
‘ ファイルを開く (ForReading = 1)
Set ts = fso.OpenTextFile(filePath, 1, False)
carryOver = “” ‘ 前回の読み込みで中途半端になったデータを持ち越すバッファ
WScript.Echo “— 巨大ファイルのストリーミング・パース処理を開始します —”
‘ ファイルの終端 (AtEndOfStream) に達するまでループ
Do While Not ts.AtEndOfStream
‘ 指定バイト数だけ安全に読み込む
‘ ※TextStream.Readは文字数指定ですが、マルチバイト文字を考慮したブロック制御の概念です
readBytes = ts.Read(chunkSize)
‘ 前回の持ち越しデータと今回読み込んだデータを結合
buffer = carryOver & readBytes
‘ 【重要】ここでブロック内の改行コード(CRLFまたはLF)を走査し、
‘ 完全な1行になっている部分までを切り出して処理する。
‘ 途中で途切れたデータは次回に持ち越す(carryOver)。
Dim currentIndex, nextIndex
currentIndex = 1
Do
‘ 改行コード(VbCrLf または VbLf)の位置を探す
‘ ※実務ではVbCrLf (Chr(13) & Chr(10)) や VbLf (Chr(10)) の両方に対応させます
nextIndex = InStr(currentIndex, buffer, vbCrLf)
If nextIndex = 0 Then
nextIndex = InStr(currentIndex, buffer, vbLf)
End If
If nextIndex > 0 Then
‘ 完全な1行を切り出し
lineStr = Mid(buffer, currentIndex, nextIndex – currentIndex)
‘ ————————————————–
‘ 【ここに実際の行データ処理ロジックを記述します】
‘ 例: Call ProcessLine(lineStr)
‘ ————————————————–
‘ 次の検索開始位置へ進む(改行コードの文字数分スキップ)
If Mid(buffer, nextIndex, 2) = vbCrLf Then
currentIndex = nextIndex + 2
Else
currentIndex = nextIndex + 1
End If
Else
‘ 改行が見つからない場合、バッファの残りを「持ち越しデータ」として保持
If currentIndex <= Len(buffer) Then
carryOver = Mid(buffer, currentIndex)
Else
carryOver = ""
End If
Exit Do
End If
Loop
' メモリ解放の気配り(巨大ループでのガベージコレクションを促す)
buffer = ""
Loop
' 最後に余った持ち越しデータがあれば処理
If Len(carryOver) > 0 Then
‘ 最終行の処理
‘ Call ProcessLine(carryOver)
End If
ts.Close
Set ts = Nothing
Set fso = Nothing
WScript.Echo “— 処理が正常に完了しました —”
End Sub
‘ 実行テスト用コード(適宜パスを変更してください)
‘ Call ParseHugeFileSafely(“C:\Logs\massive_log.txt”, 65536) ‘ 64KB単位でブロック処理
—
コードの深掘り解説:なぜこの書き方が「プロフェッショナル」なのか?
1. `carryOver`(持ち越しバッファ)の概念
ファイルを一定のサイズ(例:64KB)ごとに区切って読み込むと、「行の途中でチャンクが途切れてしまう」という問題が必ず発生します。
例えば、次のようなデータがあったとします。
> `[チャンク1の終わり] …エラーが発生しました: 配`
> `[チャンク2の始まり] 列のインデックスが範囲外です …`
これをそのままパースすると、ログが分断されて意味不明になってしまいますよね。
プロフェッショナルのコードでは、「途切れてしまった中途半端な末尾の文字列」を `carryOver` 変数に退避し、次のブロックの先頭にくっつけて再構築します。これにより、どんなに巨大なデータや変態的な改行なしファイルであっても、絶対にデータをロストしません。
2. メモリの自発的な解放とガベージコレクションへの配慮
VBScriptの背後にあるメモリ管理は、自動とはいえ万能ではありません。巨大な文字列変数を使い回すときは、処理のループの節目で `buffer = “”` のように明示的に空文字列を代入してメモリを解放するのが、落ちないスクリプトを書くための職人技です。
—
陥りやすい罠とエラー回避の心得
- 文字コード(BOM / UTF-8 / Shift-JIS)の罠
FSOの `OpenTextFile` はデフォルトでシステム固有のエンコーディング(日本語環境ならShift-JISなど)で開きます。もしUTF-8(BOM付き/なし)の巨大ファイルを扱う場合、そのままFSOで読むと文字化けの嵐になります。厳密にやるなら、ADODB.Streamを組み合わせるアプローチが必要になりますが、それはまた別の機会に。
- チャンクサイズ(ChunkSize)のチューニング
小さすぎるとI/O(ディスクアクセス)の回数が増えて処理が遅くなり、大きすぎるとメモリ圧迫のリスクが高まります。実務では `65536`(64KB)から `1048576`(1MB) あたりをベンチマークしながら最適値を見つけるのが黄金律です。
—
おわりに
いかがでしたでしょうか?
今回は、VBScriptにおけるFSOの限界を突破し、巨大ファイルを安全に料理するための「ブロック境界パースアルゴリズム」を解説しました。
「VBScriptなんて古い言語だし、どうせエラーが出たら諦めるしかない」――そんな風に考えていた時期が、あなたにもあったかもしれません。しかし、裏側の仕組み(メモリモデルやストリームの挙動)を正しく理解し、適切なアルゴリズムを組めば、VBScriptは今でも現場の最前線で頼れる強靭な相棒として機能してくれます。
ここをクリアしたあなたなら、もうどんなに巨大なログファイルが持ち込まれても怖くないはずです。
日々の業務自動化エンジニアリングを、もっとスマートに、もっとエレガントに楽しんでいきましょう!
