大文件去重必须用 bufio.Scanner 边读边判重,避免 os.ReadFile 导致 OOM;需清理空格、BOM 等再入 map[string]struct{};保序需额外记录首次出现顺序;超千万行应采用布隆过滤器或哈希分治。

大文件去重必须用 bufio.Scanner,别加载全文件
几 GB 的日志或导出数据直接 os.ReadFile 会 OOM——Go 进程内存爆掉不是报错,而是被系统 kill。核心是“边读边判重”,不保留原始内容副本。
-
bufio.Scanner默认缓冲 64KB,自动处理\r\n→\n,比ReadLine()更稳 - 每行用
strings.TrimSpace()再进 map,否则"abc"和"abc "被视为不同 - 空行、BOM(
\ufeff)、尾部制表符都得清理,不然重复逻辑失效 - map 类型必须是
map[string]struct{},不是map[string]bool——省几十 MB 内存
去重后要保序?别依赖 map 迭代顺序
Go 的 map 迭代顺序是随机的(从 1.12 开始),哪怕你按读入顺序插入,遍历出来也不等于原始顺序。想输出“首次出现的那行”,必须额外记位置。
- 维护一个
[]string记录“首次见到的行”,每次新行插入时同步append - 写入结果时只遍历这个 slice,不查 map 键值对
- 不要用
for range map输出——它不保证任何顺序,纯属碰运气 - 如果允许重排序(比如生成词表),改用
sort.Strings()+ 相邻比较,内存峰值更低
千万行以上就得换策略:哈希分治 or 布隆过滤器
单个 map[string]struct{} 在 1000 万行左右 GC 开始抖动,5000 万行大概吃掉 2–3GB 内存,再往上风险陡增。
- 中等规模(100 万–5000 万行):用
github.com/yourbasic/bloom做前置过滤,误判率设 0.001,查到 true 再落库二次确认 - 超大文件(>5000 万行 或 >10GB):哈希分治——
sha256.Sum32(line) % N分桶,相同内容必进同一桶,每个桶内再用map去重 - 别用
fnv哈希分桶:容易倾斜,某些桶暴涨,内存不均 - 分桶数
N建议取质数(如 97、199),避免哈希碰撞放大
跨文件去重或查重复行?别只比字符串
如果目标是“找出两个文件里完全相同的行”,字符串相等足够;但若想合并多个日志并剔除语义重复(如带时间戳的同一事件),光比行文本会漏判。
立即学习“go语言免费学习笔记(深入)”;
- 真正要基于内容去重(比如合并配置、去重 URL 列表),先标准化:统一小写、trim、decode URL、忽略注释行
- 查重复行本身不难,但“哪些行重复了”需要反查——建议用
map[string][]int记每行在原文件中的所有行号 - 硬链接判定不能只看
Ino,必须同时比Stat.Sys().(*syscall.Stat_t).Dev和Ino,否则跨设备误删 - 符号链接要去重,得先
os.Readlink()再os.Stat()目标,而不是os.Lstat()
time go run main.go 跑样本,看 RSS 峰值,再决定要不要切分或换布隆。


















