【極限実装】VB.NETで巨大CSVを爆速処理する:文字コード判定とTextFieldParserの真髄
現場で「CSV読み込みツール」を書くとき、多くのエンジニアが犯すミスがある。`File.ReadAllLines`で全行メモリにぶち込み、`String.Split(“,”)`で安易にパースする手法だ。
これらは、ファイルサイズが数MBを超えた瞬間に破綻する。メモリ枯渇によるOOM(Out of Memory)エラー、あるいはクォーテーションで囲まれたカンマを含むフィールドによるパース崩れ。これらは「プロの仕事」とは呼べない。
今回は、数GB規模の巨大ファイルに対しても、環境を選ばず、文字コードの壁を乗り越えて「死なない」処理を実装するための設計術を伝授する。
—
1. なぜ TextFieldParser なのか?
.NETには `StreamReader` や `CsvHelper` など多様な選択肢がある。しかし、レガシーなVB.NET環境や、外部ライブラリを持ち込めない厳格なセキュリティ要件下では、標準の `Microsoft.VisualBasic.FileIO.TextFieldParser` が最強の武器となる。
- 理由1:ストリーム処理:全行を一括読み込みせず、1行ずつイテレータとして処理するため、メモリ消費が一定に保たれる。
- 理由2:複雑なCSV仕様への対応:`”Data, with comma”`のようなクォーテーション付きフィールドを自動で解釈してくれる。自前でSplitを書くのは、バグを製造する行為に等しい。
—
2. 文字コードの「自動判定」という罠
「UTF-8かShift_JISか分からない」という状況は実務では日常茶飯事だ。ここで重要なのは、「ファイルの先頭数キロバイトをサンプリングして判定し、その結果をもとにストリームを開き直す」という二段構えのアプローチである。
以下に、実務でそのまま使える堅牢な実装を示す。
実装:高速かつ堅牢なCSVリーダー
Imports System.IO
Imports System.Text
Imports Microsoft.VisualBasic.FileIO
Public Class CsvProcessor
”’
”’
Public Sub ProcessCsv(filePath As String)
‘ 1. 文字コード判定(先頭のみをサンプリング)
Dim enc As Encoding = DetectEncoding(filePath)
‘ 2. TextFieldParserでストリーム処理
Using parser As New TextFieldParser(filePath, enc)
parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(“,”)
parser.HasFieldsEnclosedInQuotes = True ‘ クォーテーション対応
parser.TrimWhiteSpace = True
While Not parser.EndOfData
Try
Dim fields As String() = parser.ReadFields()
‘ ここでビジネスロジックを実行
‘ ※大きなオブジェクトを作らず、即座にDBへ投げるか集計する
ProcessRow(fields)
Catch ex As MalformedLineException
‘ 不正な形式の行をスキップしてログ出力
Console.WriteLine($”Skipping bad line: {ex.Message}”)
End Try
End While
End Using
End Sub
”’
”’
Private Function DetectEncoding(path As String) As Encoding
‘ UTF-8 (BOMあり) を優先し、なければShift_JISで開くのが日本の現場の定石
Using fs As New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)
Dim bom(2) As Byte
fs.Read(bom, 0, 3)
If bom(0) = &HEF AndAlso bom(1) = &HBB AndAlso bom(2) = &HBF Then Return Encoding.UTF8
End Using
‘ 必要に応じてUdeライブラリ等を導入し、Shift_JIS/UTF-8を判定する
Return Encoding.GetEncoding(“Shift_JIS”)
End Function
Private Sub ProcessRow(fields As String())
‘ 必要なデータ処理をここに
End Sub
End Class
—
3. 実務で「死なない」ための3つの鉄則
① メモリ管理を意識せよ
`While`ループ内で、読み込んだ行のデータをスタティックなリストに溜め込み続けていないか? 巨大なCSVを処理する場合、「読み込む→処理する→破棄する」のサイクルをループ内で完結させろ。リストへの蓄積は、件数が多い場合に即座にメモリパンクを招く。
② DB連携は「バルクインサート」一択
CSVを1行ずつ読み込んで、そのままDBへ1行ずつ `INSERT` していないだろうか? ネットワークの往復回数(ラウンドトリップ)がボトルネックになり、処理が終わらない。
- 解決策:`DataTable` に1,000〜5,000行単位で詰め、`SqlBulkCopy` クラスで一括流し込みを行うのが現代のベストプラクティスだ。
③ エラーハンドリングの境界線
`MalformedLineException` を適切にキャッチせよ。途中で1行だけデータが壊れていたからといって、全体の処理を止めるのは無責任だ。エラー行は別ファイルに退避させ、残りの正常データは処理を継続する設計こそが、業務現場で求められる「止まらないシステム」である。
—
最後に:エンジニアとしての矜持
「動くコード」を書くのはジュニアでもできる。だが、「数百万件のデータが来ても、文字コードが化けても、メモリを食い尽くさずに淡々と仕事をこなすコード」を書くのが、我々アーキテクトの仕事だ。
TextFieldParserは古くからあるツールだが、使いこなせばこれ以上ないほどに堅牢だ。今回示した設計をベースに、君のプロジェクトの要件に合わせてロジックを磨き上げてほしい。
技術は裏切らない。だが、設計の甘さは必ず君の休日を奪いに来る。最初から「重い処理」を想定してコードを書く癖をつけよう。健闘を祈る。
