快速文件hash

水一篇2019年的旧文。

最近打算把家里服务器上的文件理一下,想把重复的文件找出来,虽然我已经用了ZFS的dedup,实际占用空间并不会重复,但是还是觉得有必要理一下……

写个程序扫描一遍并不复杂,但是要判断文件是不是重复就比较麻烦,可靠的方法当然是做全文件HASH,但是对一个以T为单位的硬盘来说,这样效率太低了,所以写了一段小代码来做一个快速的HASH。

    def get_filemd5(fullpath, filename):
        file_size = get_filesize(fullpath, filename)
        block_count = int((QUICK_HASH_SIZE + BUFSIZE / 2) / BUFSIZE
                          if QUICK_HASH_SIZE else file_size / BUFSIZE)
        block_size = file_size * 1.0 / block_count if QUICK_HASH_SIZE \
            else BUFSIZE
        m = hashlib.md5()
        with open(os.path.join(fullpath, filename), "rb") as f:
            for i in xrange(block_count):
                pos = int(i * block_size / MINSIZE) * MINSIZE
                f.seek(pos, 0)
                buf = f.read(BUFSIZE)
                m.update(buf)
            remaining = file_size - f.tell()
            if remaining > 0:
                if QUICK_HASH_SIZE and remaining > BUFSIZE:
                    f.seek(file_size - BUFSIZE, 0)
                    remaining = BUFSIZE
                buf = f.read(remaining)
                m.update(buf)
        return m.hexdigest()

HASH算法是直接用MD5,简单方便。其中定义了三个常量:QUICK_HASH_SIZE、BUFSIZE和MINSIZE。

其中MINSIZE定义了对齐的大小,BUFSIZE定义了HASH分块的大小,QUICK_HASH_SIZE则是定义了需要做快速HASH的文件大小阈值。

方法很简单:文件小于QUICK_HASH_SIZE,则做全文件HASH,大于QUICK_HASH_SIZE则按BUFSIZE大小间隔取样,总取样数据大小为QUICK_HASH_SIZE。

每个BUFSIZE块在文件中的分布为均匀的,但是每块又以MINSIZE对齐,另外,因为通常文件最经常被修改的部分是最后的部分,所以最后一块一定是与文件结尾对齐,以保证HASH部分包括文件的结尾。

当然,因为没有对全文件做HASH,所以没有被HASH到的部分如果有修改,是不会被发现的,但是对我的需求来说基本不会有这种情况,所以不要紧,但是这样可以让HASH速度大大加快,特别是对那些几个G的大文件(不许联想,我说的是操作系统的安装ISO镜像文件之类)。

推送到[go4pro.org]