C#文件内容去重 C#如何高效处理大文本文件并去除重复行

来源:这里教程网 时间:2026-02-21 17:41:32 作者:

HashSet<string></string>
流式去重,避免内存爆炸

直接把几 GB 的文件全读进

List<string></string>
再用
Distinct()
,基本等于触发
OutOfMemoryException
。关键不是“去重逻辑”,而是“不加载全文到内存”。
HashSet<string></string>
是唯一能兼顾查重速度(O(1))和内存可控的结构,但必须配合逐行读取。

File.ReadLines(path)
(不是
File.ReadAllLines
),它返回
IEnumerable<string></string>
,真正按需读取,不缓存整文件
HashSet<string></string>
只存已见行的哈希值,字符串本身仍只在当前行生命周期内存在;重复行直接跳过,不进集合
若文件含 BOM 或混合换行符(
\r\n
/
\n
),先用
line.TrimEnd('\r', '\n')
统一处理,否则
"abc\r\n"
"abc"
被视为不同行

处理超长行或特殊编码时,别硬扛默认编码

默认

File.ReadLines
用 UTF-8,但遇到 GBK 编码的中文日志、或某行末尾有未闭合引号导致解析错位,就会乱码或截断——此时去重结果全错。必须显式指定编码,且对单行长度设防。

改用
new StreamReader(path, Encoding.GetEncoding("GBK"))
+
ReadLine()
循环,比
File.ReadLines
更可控
加长度检查:若
line?.Length > 10_000_000
,记录警告并跳过(防恶意超长行拖垮哈希计算)
若需忽略大小写去重,初始化
HashSet<string></string>
时传
StringComparer.OrdinalIgnoreCase
,别自己调
ToLower()
——后者会额外分配字符串对象

写入结果时用
StreamWriter
批量刷盘,别每行
Flush()

去重后写新文件,如果对每一行都调

sw.WriteLine(line); sw.Flush();
,磁盘 I/O 次数翻几十万倍,速度暴跌。缓冲区大小和刷盘时机得手动管。

构造
StreamWriter
时指定缓冲区:
new StreamWriter(outputPath, false, Encoding.UTF8, 64 * 1024)
(64KB 缓冲)
完全写完再
Close()
Dispose()
,让底层自动刷盘;除非中途崩溃风险高,否则别主动
Flush()
若输出需保持原文件编码,从输入流读取时记下
streamReader.CurrentEncoding
,传给
StreamWriter
构造函数

真遇到 10GB+ 文件,考虑分块哈希 + 外部排序

当单机内存不足(比如只有 4GB RAM 却要处理 12GB 日志),

HashSet
仍可能因哈希碰撞或字符串驻留膨胀而 OOM。这时得放弃“一行一判”,改用确定性分片。

先按首字母或哈希前缀把原文件拆成多个小文件:
line.GetHashCode() % 100
→ 分到 00–99 个临时文件
每个小文件单独用
HashSet
去重,生成中间去重文件
最后合并所有中间文件,再跑一次去重(此时数据量已大幅下降,可全载入内存) 注意:此法不保原始顺序;若需稳定序,改用
SortedSet<string></string>
替代
HashSet
,但性能降约 30%

实际最常被忽略的是换行符标准化和编码探测——很多“去重无效”问题,根源是

"abc"
"abc "
(带空格)或
"abc\uFEFF"
(带 BOM)被当成不同行,而不是算法本身慢。

相关推荐