应使用 StreamReader + StreamWriter 流式逐行处理大文件,避免 File.ReadAllLines 导致内存溢出;读取 UTF-8 BOM 文件需显式指定 Encoding.UTF8;可 for 循环跳过前 N 行。

用 StreamReader + StreamWriter 逐行处理大文件,别用 File.ReadAllLines
内存爆掉是清洗脚本最常见的崩溃原因。一两万行还行,但真实业务里动辄几十MB的CSV或日志,File.ReadAllLines 会把全部内容加载进内存,尤其当每行含大量空格、乱码或超长字段时,GC压力陡增,甚至直接 OutOfMemoryException。
实操建议:
- 始终用
StreamReader按行读取,配合StreamWriter实时写入清洗后结果,流式处理不积压 - 对 UTF-8 带 BOM 的文件,显式指定
new StreamReader(path, Encoding.UTF8),否则可能读出\ufeff开头的脏字符 - 若需跳过前 N 行(如表头),用
for (int i = 0; i ,别依赖 <code>ReadLine().Skip()—— 后者仍会先全读进内存
字符串清洗优先用 Span<char></char> 和 ReadOnlySpan<char></char>,别反复 .Replace()
原始数据里常见连续空格、制表符、不可见控制字符(如 \u0000、\u200B),用传统 string.Replace(" ", "") 链式调用会产生大量中间字符串,GC频繁且性能差。
实操建议:
- 对单行清洗,转成
Span<char></char>后原地裁剪:用line.AsSpan().Trim()去首尾空白,再用line.AsSpan().TrimEnd('\r', '\n')清理换行符 - 过滤不可见字符时,避免正则:用
foreach (var c in line.AsSpan()) { if (!char.IsControl(c) && !char.IsWhiteSpace(c) || c == ' ') yield return c; } - 如果必须替换,用
StringBuilder预分配容量:var sb = new StringBuilder(line.Length);,再循环追加有效字符
日期/数字字段解析失败时,用 DateTime.TryParseExact 和 decimal.TryParse 守住边界
脏数据最常卡在类型转换上:Convert.ToDateTime("2023-13-01") 直接抛异常,int.Parse("N/A") 也一样。硬 try-catch 全局吞异常看似省事,实则掩盖字段级问题,导致后续计算错得离谱。
实操建议:
- 日期字段必须指定格式数组:
DateTime.TryParseExact(value, new[] { "yyyy-MM-dd", "d/M/yyyy", "yyyyMMdd" }, null, DateTimeStyles.None, out var dt),别依赖Parse的模糊匹配 - 数字字段统一用
decimal.TryParse(不是double),避免浮点精度丢失;对空、"NULL"、"-" 等占位符,提前value.Trim() == "" || value.Equals("NULL", StringComparison.OrdinalIgnoreCase)判断 - 解析失败不静默跳过,写入清洗日志文件(带原行号、原始值、错误类型),否则你永远不知道哪列数据其实全丢了
输出 CSV 时小心逗号、换行、引号 —— 用 Microsoft.VisualBasic.FileIO.TextFieldParser 反向验证
手写 CSV 输出极易翻车:字段含逗号没加双引号、含换行符没转义、双引号没替换成两个双引号(" → "")。下游系统一读就错行,排查起来极耗时间。
实操建议:
- 别自己拼接:
$"\"{value.Replace("\"", "\"\"")}\",..."容易漏边界条件;改用System.Text.Json.JsonSerializer.SerializeToUtf8Bytes临时转 JSON 再提取字段,或直接引用Microsoft.VisualBasic(.NET Core 5+ 已内置) - 写完清洗结果后,用
TextFieldParser读一遍刚生成的文件:using var parser = new TextFieldParser(outputPath) { Delimiters = new[] { "," } };,能正常读出每行字段数,才算真正合规 - 若字段值本身含双引号,必须包裹且内部转义;但若整字段为空,不要写成
"",应保持空字符串不加引号(除非标准强制要求)
最麻烦的不是写清洗逻辑,而是脏数据的“不规律性”——同一列里可能混着 ISO 日期、中文日期、纯数字时间戳、甚至“昨天”“下周三”。这类语义型脏数据,规则清洗无解,得留接口接入 NLP 或人工标注环节。先跑通确定性清洗,再考虑这个。


















