【テクニカル・上級編】実務中級者向け:VB.NETでのCSV・TSVファイル高速読み込み:TextFieldParserを使った文字コード自動判定と大容量データ処理の実装術 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

巨大CSVをねじ伏せる:VB.NETにおける高速パースと文字コード自動判定の極意

現場で「数ギガバイトのCSVを読み込め」という要求に直面したとき、安易に `File.ReadAllLines` を選択するエンジニアは即刻プロジェクトから外すべきだ。メモリを食い潰し、GC(ガベージコレクション)を暴走させ、システムを停止させるのがオチだからだ。

我々が向き合うべきは、メモリを極限まで節約し、かつ「文字化け」というレガシーの呪縛を断ち切る実装である。今日は、`TextFieldParser` を核とした、実務で戦える「真の高速読み込み」の知見を授ける。

1. なぜ TextFieldParser なのか

`StreamReader` で自前で Split する手法もあるが、CSV特有の「ダブルクォーテーションで囲まれたカンマ」や「改行コードを含むフィールド」を正しくパースするには、複雑なステートマシンを実装する必要がある。

`Microsoft.VisualBasic.FileIO.TextFieldParser` は、VBA時代から培われたレガシーな血統を持ちながら、内部的には非常に堅牢なパースロジックを持っている。これを適切にラップし、メモリ消費を抑えながらストリーム処理を行うのが、最もコストパフォーマンスが高い。

2. 文字コードの罠を突破する:自動判定の真実

「Shift_JISかUTF-8か分からない」という環境は、日本の現場の日常だ。これを力技で解決する唯一の方法は、先頭数キロバイトをサンプリングし、バイトパターンから推論することだ。

`System.Text.Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)` を忘れずに行うこと。これがないと、.NET Core以降の環境では Shift_JIS は存在しないものとして扱われる。

3. 実践:高速・低メモリCSVパース・エンジン

以下に、メモリを効率的に利用し、かつ文字コードを自動判定するクラスの原型を示す。

Imports System.IO
Imports System.Text
Imports Microsoft.VisualBasic.FileIO

Public Class CsvReaderEngine
‘ .NET Core/5+ での Shift_JIS 対応に必須
Shared Sub New()
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)
End Sub

Public Sub ProcessLargeCsv(filePath As String)
‘ 文字コード自動判定
Dim encoding As Encoding = DetectEncoding(filePath)

‘ Stream を明示的に制御する
Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan)
Using parser As New TextFieldParser(fs, encoding)
parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(“,”)
parser.HasFieldsEnclosedInQuotes = True
parser.TrimWhiteSpace = True

‘ メモリを圧迫しないよう、1行ずつ読み込む
While Not parser.EndOfData
Try
Dim fields As String() = parser.ReadFields()
‘ ここでパース後のデータを処理
‘ 巨大な配列を生成せず、必要なデータだけを抽出すること
ProcessRecord(fields)
Catch ex As MalformedLineException
‘ 不正な行はログに落として続行する
Continue While
End Try
End While
End Using
End Using
End Sub

‘ BOMなしUTF-8やShift_JISを判別する簡易メソッド
Private Function DetectEncoding(filePath As String) As Encoding
Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)
Dim buffer(3) As Byte
fs.Read(buffer, 0, 3)
‘ BOMチェック
If buffer(0) = &HEF And buffer(1) = &HBB And buffer(2) = &HBF Then Return Encoding.UTF8
‘ 必要に応じてUde等のライブラリを利用するか、頻出文字コードを試行する
Return Encoding.GetEncoding(“Shift_JIS”)
End Using
End Function

Private Sub ProcessRecord(fields As String())
‘ 業務ロジックの実装
End Sub
End Class

4. チーフアーキテクトからの「極限の知見」

メモリ最適化の要諦

上記のコードでは `FileOptions.SequentialScan` を指定している。これはOSに対し「このファイルは先頭から順番に読む」と宣言するフラグだ。これにより、OSのファイルキャッシュ戦略が最適化され、ディスクI/Oのボトルネックが劇的に改善される。

オブジェクトの明示的解放

`Using` ブロックは必須だが、数百万行の処理を行う際は、ループ内で生成する一時オブジェクトの数にも気を配れ。不要な文字列結合(`&`演算子)はループ内で行わず、可能な限り `StringBuilder` を使い、GCの発生回数を最小限に抑えること。

レガシー環境とWindows API

もし、読み込み速度がどうしてもボトルネックになる場合、`Win32 API` の `CreateFile` を `FILE_FLAG_SEQUENTIAL_SCAN` オプション付きで叩き、ハンドルを `FileStream` に渡す手法もある。だが、まずは上記のストリーム制御で十分戦えるはずだ。

最後に

VB.NETは「古い」のではない。「完成されている」のだ。適切にAPIを理解し、メモリレイアウトを意識すれば、現代の言語と比較しても何ら遜色のないパフォーマンスを発揮する。

あなたが書くそのコードが、システムの安定稼働を左右する。IDEの補完に頼る前に、一度「メモリの中で何が起きているか」を想像してほしい。それができる者だけが、真の自動化エンジニアと名乗れるのだ。

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