文章首发于个人博客
背景
想自己做这个功能,主要是因为Duplicate Cleaner
这个商业软件只有几天的试用时间,而且文件去重这个逻辑也非常简单。
{{}} graph TD a[获取文件清单及大小] --> b[按大小分组] --> c[排除只有一个文件的组] --> d[计算文件Hash值] --> e[按Hash值分组] --> f[排除只有一个文件的组] --> g[选择需要删除的文件] --> h[删除] {{}}
问题
计算文件Hash
值,使用了hash.Hash
接口,自然也用到了goroutine
来缩短耗时,但是在测试的时候发现功能不太好用,时好时坏,准确说是有时能获取到重复列表,有时不能。
一点点排查,并且把代码段发给DeepSeek
,最终确定是因为hash.Hash
不是并发安全的。
修复方法很简单,只要在goroutine
内实例化即可。修改之后达到了预期。
go
func calcHashs(files []*FileInfo, hashName string) {
g := sync.WaitGroup{}
for _, file := range files {
g.Add(1)
go func(f *FileInfo) {
defer g.Done()
h := newHash(hashName)
hashValue, err := calcHash(f.Path, h)
if err != nil {
log.Printf("计算文件 %s 的哈希值失败: %v", f.Path, err)
return
}
f.Hash = hashValue
}(file)
}
g.Wait()
}
TODO
以上存在一个隐患,即:当文件过多时,goroutine
会爆炸,至于会有什么影响,没有进行测试,也没敢测试,担心把机器干翻。整体功能做完后,下一步进行这一处的优化。
题外话
计算Hash
值这部分,专门准备了约15G
的测试文件,和Duplicate Cleaner
比较了一下,为此还用Lazarus
写了同样的功能,三者一起比较。结果不得不令人叹服:
-
go
和Lazarus
的耗时非常接近,但Duplicate Cleaner
只用了go
和Lazarus
一半的时间 -
go
不同hash
算法之间耗会有几十秒不等
的差异,Duplicate Cleaner
各算法耗时只有几秒
的差异
看来收费果然是有收费的理由的!