单文件去重超千万行须弃map[string]struct{},改用布隆过滤器或哈希分治;小文件(≤5000万行或≤10gb)适用哈希分治+桶内map去重,核心是相同内容进同一桶,推荐sha256.sum32()%n分桶,避免fnv哈希倾斜。

单文件去重用 map[string]struct{} 最快最稳,但超过千万行就得换布隆过滤器或哈希分治;直接读全文件进内存再去重,OOM 风险极高。
小文件(
适合日志、配置、URL 列表等常见文本。关键不是“读完再处理”,而是边读边判重,避免中间切片分配。
- 用
bufio.Scanner逐行读,比ReadLine()更安全(自动处理换行符和缓冲) - map 类型必须是
map[string]struct{},不是map[string]bool——省几 MB 到上百 MB 内存 - 别用
scanner.Text()后直接当 key:空行、BOM、尾部空格会导致误判;建议strings.TrimSpace(line)后再查 - 写入新文件时保持原始顺序,但 map 迭代不保序,所以必须用额外 slice 记录首次出现顺序
seen := make(map[string]struct{})
var uniqueLines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" { continue }
if _, exists := seen[line]; !exists {
seen[line] = struct{}{}
uniqueLines = append(uniqueLines, line)
}
}
// 再遍历 uniqueLines 写出
中等规模(100 万–5000 万行):布隆过滤器 + 二次校验
纯内存 map 开始吃紧,GC 延迟明显上升,且无法容忍误判时(比如金融类去重),必须引入概率型前置过滤。
- 用
bloom.New(50_000_000, 0.001)初始化,预估量设大 20%,误判率 0.1% 是工程平衡点 -
bloom.Test()返回false→ 绝对没出现过,直接保留;返回true→ 查 Redis 或本地 SQLite 确认是否真重复 - 布隆过滤器本身不支持删除,若业务需“撤回某行”,必须在后端存储中标记状态,不能只依赖 bloom
- 别自己实现 bloom —— 用
github.com/yourbasic/bloom,它支持序列化,重启后可复用
超大文件(>5000 万行 或 >10GB):哈希分治 + 小文件内去重
内存不够装不下全部 key,也不能依赖外部服务,唯一可靠路径是分而治之。核心是“相同内容必须进同一桶”。
- 对每行做
sha256.Sum256([]byte(line)).Sum32() % N分桶(N=100 是起点),别用hash/fnv—— 它抗碰撞弱,倾斜严重 - 生成
out_00到out_99一百个小文件,每个再用map[string]struct{}去重并合并输出 - 若某个桶文件仍超 500MB,说明哈希不均,对该桶单独换一个哈希函数(如加盐:
sha256.Sum256([]byte("salt_"+line)))再拆 - 绝对不要先排序再双指针——10GB 文件无法 in-memory sort,外部排序 I/O 开销是分治的 3–5 倍
去重后还要删原文件?小心硬链接和权限问题
用 Go 删除文件不只是调 os.Remove(),尤其在 Linux/macOS 下处理日志轮转或容器挂载卷时容易翻车。
-
os.SameFile(old, new)检查是否为同一 inode,避免误删软链目标或硬链接源 - 写新文件后,用
os.Chmod(newPath, oldInfo.Mode())复制权限,否则 cron 脚本可能因无执行位失败 - 删除前先
os.Rename(old, old+".bak")做原子备份,尤其在线上数据管道中——恢复比重跑快十倍 - Windows 下注意
os.Remove()对正在被记事本打开的文件会失败,得加重试 +syscall.ERROR_SHARING_VIOLATION判断
真正难的不是写对一行 seen[line] = struct{}{},而是预估数据膨胀系数、选对哈希分布策略、以及在删文件前确认那个 .bak 真的写完了——这些细节不压测根本暴露不出来。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











