C#操作大型CSV文件 C#如何使用CsvHelper等库高效处理大CSV

1次阅读

csvhelper读大文件oom是因为readrecords()默认全量加载,应改用getrecord()单行读取、禁用缓冲、手动控制游标,并配合streamreader/streamwriter流式处理。

C#操作大型CSV文件 C#如何使用CsvHelper等库高效处理大CSV

为什么直接用 CsvHelper 读大文件会 OOM?

因为默认的 ReadRecords<t>()</t> 会把整个 CSV 加载进内存,哪怕你只想要前 100 行。1GB 的 CSV 很可能触发 OutOfMemoryException,尤其在 32 位进程或内存受限环境(如 azure App Service 免费层)。

关键不是库不行,而是调用方式错了——必须禁用自动缓冲、跳过反射式全量解析、手动控制读取节奏。

  • CsvReader 要配 new CsvConfiguration { BufferSize = 8192, ShouldSkipRecord = ... },避免默认 64KB 缓冲在长行时爆涨
  • 永远不用 GetRecords<t>()</t>,改用 GetRecord<t>()</t> 单条读取 + 显式 Read() 移动游标
  • 如果字段少、结构固定,跳过泛型映射,直接用 parser.ReadField()字符串,省掉 Convert.ChangeType 开销

如何边读边写,避免中间存全量数据?

典型场景:清洗 500 万行 CSV,过滤掉空邮箱、标准化手机号、写入新文件。这时内存里不该存在“原始列表”或“结果列表”,而应是“当前行 → 处理 → 写入”流水线。

用两个独立的 CsvReader / CsvWriter 实例,共享同一个 StreamReader / StreamWriter,并确保 StreamWriter 启用 AutoFlush = true 或定期 Flush()

using var reader = new StreamReader("input.csv"); using var writer = new StreamWriter("output.csv") { AutoFlush = true }; using var csvReader = new CsvReader(reader, config); using var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture);  csvWriter.WriteField("name"); csvWriter.WriteField("phone"); csvWriter.NextRecord();  while (csvReader.Read()) {     var name = csvReader.GetField("full_name");     var rawPhone = csvReader.GetField("mobile");     if (!string.IsNullOrWhiteSpace(rawPhone))     {         var cleaned = Regex.Replace(rawPhone, @"D", "");         csvWriter.WriteField(name);         csvWriter.WriteField(cleaned);         csvWriter.NextRecord();     } }

注意:csvReader.Read() 是关键驱动,不是 foreach —— 后者隐式调用 GetRecords,又掉坑里了。

遇到中文乱码、bom、超长字段怎么办?

windows 记事本保存的 CSV 常带 UTF-8 BOM,CsvHelper 默认不识别,会把 BOM 当作第一列内容;excel 导出的 CSV 可能用 GBK;某行字段含千字文加换行符,会撑爆默认缓冲区。

  • 读取前先检测 BOM:var bom = new byte[3]; stream.Read(bom, 0, 3); if (bom.SequenceEqual(new byte[]{0xEF, 0xBB, 0xBF})) ...,然后用 new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)
  • 强制指定编码:构造 StreamReader 时传 Encoding.UTF8Encoding.GetEncoding("GB2312"),别依赖自动探测
  • 超长字段:设 Configuration.DetectDelimiter = false + Configuration.Delimiter = ",",关掉耗时的分隔符探测;加大 BufferSize 到 65536,但别无脑调大——它影响的是单次 Read() 的底层 IO 批量,不是内存驻留总量

替代方案:什么情况下该换 StreamReader + 手撕?

当 CSV 极度简单(无引号、无换行、无逗号在字段内)、且性能压到极限(比如每秒处理 100MB+),CsvHelper 的字段解析、类型转换、验证逻辑反而成瓶颈。

此时直接用 StreamReader.ReadLine() + Split(',') 更快,但必须满足:所有字段都不含逗号、双引号、换行符。否则 Split 会错切。

  • 安全做法:用 microsoft.VisualBasic.FileIO.TextFieldParser(.NET Core 5+ 可用),它原生支持带引号的 CSV,比手写状态机稳,又比 CsvHelper 轻量
  • 若需并发处理,别用单个 CsvReader,改用 File.ReadLines() 分块(按行数切,非字节数),再丢给 Parallel.ForEach,每块内用 TextFieldParser
  • 记住:CSV 规范本身允许引号包裹含逗号字段,只要业务方保证“导出时不启用引号”,才能放心手撕

真正难的从来不是读几百万行,而是确认你的 CSV 真的“规整”——字段里的回车、BOM 的有无、Excel 和 Python pandas 写出的编码差异,这些细节漏查一行,后面全白跑。

text=ZqhQzanResources