不能仅用os.stat.modtime做增量判断,因nfs、容器挂载或git工作区中modtime易被批量重写或延迟更新,导致touch误触发备份或内容变更却漏备;大文件哈希须流式分块计算,避免io.readall引发内存溢出。

为什么不能只用 os.Stat.ModTime 做增量判断
ModTime 在 NFS、容器挂载卷、Git 工作区里经常被批量重写或延迟更新;touch 一下文件就触发备份,但内容根本没变;反过来,内容变了但 ModTime 没刷新(比如某些编辑器覆盖写入不更新时间戳),就会漏备。仅靠它做判断,不是多备就是少备。
大文件哈希必须分块流式计算,别用 io.ReadAll
一次性 io.ReadAll 读 2GB 文件,内存直接爆,还可能被 OS OOM killer 干掉。正确做法是复用 hash.Hash 实例,配合 bufio.NewReaderSize(f, 1(1MB 缓冲)分块读取:
h := sha256.New() r := bufio.NewReaderSize(f, 1 <p>注意:<code>sha256.Sum256</code> 是值类型,只适合已知字节;流式场景必须用 <code>hash.Hash</code> 接口。</p> <h3>路径 + 大小 + 哈希三元组才是可靠比对 key</h3> <p>只用哈希当 key 会撞车(不同文件哈希偶然相同);只用路径会忽略硬链接;只用大小更不准。实际应组合:</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6460" title="Golang Naming"><img src="https://img.php.cn/upload/skill/000/000/081/179094616043400.jpg" alt="Golang Naming" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="overflowclass">Golang Naming</a> <p class="overflowclass">Go(Golang)命名规范 — 包括包、构造函数、结构体、接口、常量、枚举、错误、布尔值、接收器、getter/setter、函数等。</p> </div> <a rel="nofollow" href="/xiazai/skill6460" title="Golang Naming" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
-
map[string]struct{ Size int64; Hash [32]byte },key 用filepath.ToSlash(path)统一斜杠格式(WindowsC:\a\b.txt→C:/a/b.txt) - 避免用绝对路径做 key,迁移后失效;相对路径从备份根目录起算
- 硬链接文件大小和哈希一致,但路径不同——这正是你希望保留的“多个入口”,不应去重
并发哈希计算时,每个 goroutine 必须独占 hash.Hash 实例
hash.Hash 不是并发安全的,共享实例会导致哈希值错乱甚至 panic。常见错误写法:
var h hash.Hash = sha256.New() // 全局或闭包变量
go func() { h.Write(...) }() // 多个 goroutine 同时写
正确做法:
- 每个 worker goroutine 内部新建
sha256.New() - 或用
sync.Pool复用*sha256.digest(需注意 Pool 对象可能被 GC 清理,务必检查非空) - 千万别把
*os.File或hash.Hash传进多个 goroutine 共享使用
真正难的不是算哈希,而是路径 normalize、硬链接识别、跨平台大小写处理——这些细节不抠清楚,增量逻辑在 Windows 或 Docker 环境里跑两轮就崩。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










