检查两个文件是不是相等的最快哈希算法是啥?

Posted

技术标签:

【中文标题】检查两个文件是不是相等的最快哈希算法是啥?【英文标题】:What is the fastest hash algorithm to check if two files are equal?检查两个文件是否相等的最快哈希算法是什么? 【发布时间】:2010-12-18 05:03:24 【问题描述】:

创建用于检查两个文件是否相等的哈希函数的最快方法是什么?

安全性不是很重要。

编辑:我正在通过网络连接发送文件,并确保双方的文件相等

【问题讨论】:

哈希函数不能告诉你两个文件是否相等。它只能告诉您两个文件是否相等。如果您只比较两个文件一次,那么读取文件并比较它们比任何哈希算法都更快。 @jemfinch:如果文件不在同一个文件系统上,哈希函数是一种更快的方法来证明文件是相同的。 只要哈希不能证明文件相等的概率小于所有其他可能出错的事情(例如计算机故障)的概率之和,那么一切都很好。对于 256 位哈希,您的计算机更有可能变成猫(大型动物不太可能)或一碗矮牵牛。 您没有针对这个问题充实您的用例,但其中一个可能如下所示:您希望避免获取 LARGE UNCHANGED 文件的副本。假设一个大文件的本地HASH和一个本地大文件。假设服务器有一个 LARGE 文件和该文件的当前 HASH。您可以下载 服务器 HASH 并查看它是否与本地 HASH 匹配 - 如果是,您不必获取文件的新副本。您还可以使用 HASH 和本地算法对本地 LARGE 文件进行健全性检查。 【参考方案1】:

一种方法可能是使用简单的 CRC-32 算法,并且只有当 CRC 值比较相等时,才使用 SHA1 或更稳健的方法重新运行哈希。快速 CRC-32 在任何一天都将胜过加密安全哈希。

【讨论】:

我想说散列文件无论如何都可能是 I/O 绑定的,因此您不妨使用分布良好且范围大的散列(当然任何加密散列都符合条件)。 我要在这里自相矛盾:如果只有两个长度相等的文件,那么使用散列不会比直接比较更快。如果您有许多文件并且想要找到相等的候选者,那么哈希是有意义的。 如果您通过网络比较文件(就像 OP 一样),那么读取每个文件就相当于再次通过网络重新传输文件。所以使用某种散列可能是有意义的。但我会同意第一次使用一个好的散列算法,而不是先做一个初步的 CRC32,然后再做其他事情。 @StevenSudit 它不受快速 SSD 上的 IO 限制。我有一个测试文件,其中 md5 需要一分钟,但我的 SSD 可以在 25 秒内读取文件。而且我的 SSD 用了几年了,你现在可以买更快的了。 即使只是在本地比较,如果唯一需要的结果是“相等”/“不相等”,散列可能仍然有意义,因为这允许驱动器/操作系统以最快的速度读取文件尽可能,而不是在 2 个文件之间交替使用块。【参考方案2】:

为什么要散列它?

如果您想确保两个文件相等,那么根据定义,您必须读取整个文件(除非它们实际上是同一个文件,在这种情况下,您可以通过查看文件系统上的元数据来判断)。无论如何,没有理由散列,只需阅读它们,看看它们是否相同。散列会降低效率。即使哈希匹配,您仍然不确定文件是否真的相等。

编辑:此答案是在问题指定有关网络的任何内容之前发布的。它只是询问比较两个文件。既然我知道文件之间存在网络跃点,我会说只需使用 MD5 哈希就可以了。

【讨论】:

我正在通过网络连接发送一个文件,并且会确保两边的文件是相等的。 哦,在这种情况下,只需使用真正的哈希算法。我保证你的网络会比哈希慢。 在这种情况下,使用已经存在的哈希函数。 Greg,发布了一些很好的例子。【参考方案3】:

如果它只是一个关闭,那么鉴于您必须读取两个文件以生成它们的哈希值,为什么不一次读取每个文件的少量并进行比较?

CRC 是一个非常简单的算法。

【讨论】:

+1 表示 CRC,因为 OP 要求“最快”。当然,然后他要求“确保文件相同”,这自相矛盾。 @rogerdpack crc 并不接近最快的哈希,即使使用 asm。 @OneOfOne 是的,我相信我当时并没有意识到这一点。这些天我推荐xxhash或cityhash,在这里查看我的其他答案***.com/a/11422479/32453 [显然使用crc32c它可以编译成非常快的CPU指令......虽然这不是我最初在这里指的我不认为所以你的评论是对的]【参考方案4】:

您可以尝试MurmurHash,它专门设计用于速度快,而且代码非常简单。如果 MurmurHash 返回匹配项,您可能需要第二个更安全的哈希,以确保。

【讨论】:

OP 表示这里不考虑安全性,所以我不确定为什么第二个哈希会有所帮助。相反,我建议使用 Murmur 的 64 位变体之一。 我会自相矛盾地建议较新的 128 位变体更好,然后自相矛盾地补充说,对于这个用例,我会坚持使用适当的加密哈希,例如 SHA-256。 cbloomrants.blogspot.com/2010/08/08-21-10-adler32.html 和 strchr.com/hash_functions 似乎暗示 murmurhash 比 adler/crc32 更快,只是稍微快一点。这可能都取决于实现,例如这个 sse 版本说它是一个“快速”的类似 crc 的哈希:cessu.blogspot.com/2008/11/…【参考方案5】:

对于这种类型的应用程序,Adler32 可能是最快的算法,具有合理的安全级别。对于较大的文件,您可以计算多个哈希值,例如每个 5 Mb 文件块一个,从而减少出错的机会(即哈希相同但文件内容不同的情况)。此外,这种多哈希值设置可以允许以多线程方式实现哈希计算。

编辑:(根据 Steven Sudit 的评论)如果文件很小,请注意! Adler32 的“加密”属性,或者说它的弱点,尤其是短消息是众所周知的。因此,对于小于几千字节的文件,应避免使用建议的解决方案。 无论如何,在这个问题中,OP 明确地寻求快速算法并且放弃对安全性的担忧。此外,对速度的追求可能暗示一个人正在处理“大”文件而不是小文件。在这种情况下,可能并行应用于 5Mb 文件块的 Adler32 仍然是一个非常有效的答案。 Alder32 以其简单和快速而闻名。此外,它的可靠性虽然低于相同长度的 CRC,但对于超过 4000 字节的消息来说是完全可以接受的。

【讨论】:

我不会出于任何目的推荐 Adler32。它具有可怕的特性,尤其是对于短文件。 有更快但更好的算法。想到了 MurmurHash3,但对于这个用例,我建议 I/O 速度是限制,所以 SHA-256 会很好。 (另外,请使用评论选项而不是编辑您的评论,否则我只有运气好才会知道您的回复。) 显然 adler32 “对数字不利”strchr.com/hash_functions 但 CRC32 还可以,至少在分布方面是可以的。【参考方案6】:

除非您使用非常复杂和/或缓慢的哈希,否则从磁盘加载数据将比计算哈希花费更长的时间(除非您使用 RAM 磁盘或高端 SSD)。

所以要比较两个文件,请使用以下算法:

比较尺寸 比较日期(这里要小心:这可能会给您错误的答案;您必须测试是否适合您) 比较哈希值

这允许快速失败(如果大小不同,您就知道文件不同)。

为了使事情变得更快,您可以计算一次哈希并将其与文件一起保存。还将文件日期和大小保存到这个额外的文件中,以便您快速知道何时必须重新计算哈希或在主文件更改时删除哈希文件。

【讨论】:

我已经实现了一个工作解决方案,它使用 NTFS 下的备用数据流来存储哈希值。然而,我必须做的一件事是给哈希加上时间戳,这样我就可以判断文件自上次哈希后是否被修改过。 今天的快速磁盘可以以每秒 2.5GB 的速度读取。根据我的经验,哈希远没有那么快。 @AbhiBeckert 我的论点是:如果您计算了哈希值,则不需要加载整个数据集。我的第一句话也是“除非你使用非常复杂和/或缓慢的哈希”,不是吗? @AaronDigulla 在我的情况下,我想检查大量文件的内容是否仍然与之前计算的哈希匹配,因此需要重新计算。使用 sha1 和快速 SSD 以及大量文件,哈希计算将我所有的 CPU 内核固定在 100% 上一两个小时,导致风扇旋转到最大速度,并限制时钟速度以防止过热等等等。我来这里是为了找到更有效的哈希。就强哈希而言,我认为 sha1 并不复杂或缓慢,尽管“真的”是一个相对术语。我尝试了 MD5,结果相似。 @AbhiBeckert 我明白了。 SHA 和 MD 在设计时考虑了加密(安全性比速度更重要)。这个问题可能会有所帮助:softwareengineering.stackexchange.com/questions/49550/…【参考方案7】:

您可以查看 samba/rsync 开发人员使用的算法。我没有深入研究它,但我看到它一直被提及。显然它相当不错。

【讨论】:

rsync 实际上使用的是 Adler32 算法的“滚动校验和”版本,截至 Wikipedia:en.wikipedia.org/wiki/Adler-32【参考方案8】:

xxhash 声称自己非常快速和强大,碰撞明智:

http://cyan4973.github.io/xxHash/

总体而言,有一个 64 位变体在 64 位处理器上的运行速度比 32 位处理器“更快”,但在 32 位处理器上运行速度较慢(见图)。

http://code.google.com/p/crcutil 也被认为非常快(并且在存在的情况下利用硬件 CRC 指令,这可能非常快,但如果您没有支持它们的硬件,则速度不会那么快)。不知道 CRC32c 是否与 xxHash 一样好(在冲突方面)...

https://code.google.com/p/cityhash/ 似乎与 crcutil 相似且相关 [因为如果得到指示,它可以编译为使用硬件 CRC32c 指令]。

如果您“只想要最快的原始速度”并且不太关心哈希输出的随机分布的质量(例如,对于小集合,或者速度至关重要),这里提到了一些快速算法这里:http://www.sanmayce.com/Fastest_Hash/(在某些情况下,这些“不太随机”的分布类型算法“足够好”并且非常快)。显然 FNV1A_Jesteress 是“长”字符串最快的,其他一些可能是小字符串。 http://locklessinc.com/articles/fast_hash/ 似乎也相关。我没有研究这些碰撞属性是什么。

最新热度好像是https://github.com/erthink/t1ha和https://github.com/wangyi-fudan/wyhash,而且xxhash也有轻微更新的版本。

【讨论】:

“总体而言,有一个 64 位变体在 64 位处理器上的运行速度比 32 位处理器“更快”,但在 32 位处理器上运行速度较慢(见图)。” - 好的,我认为 64 位代码针对 64 位处理器进行了优化,并且使用 64 位长整数来分块散列机制。 @BenPersonick - 在所有其他条件相同的情况下,64 位版本在 32 位处理器上的运行速度比在 64 位处理器上的速度慢是有道理的一个... 32 位处理器将不得不将 64 位块大小分成两部分,而不是一次运行它:) @warren 完全正确,如果可能的话,在 32 位 CPU 上就是这种情况,但是您不能在 32 位 CPU 上运行 64 位代码。我相信他的意思是在 64 位 CPU 上运行 64 位代码比在 64 位 CPU 上运行 32 位版本的程序运行得更快。这是意料之中的,因为这是一个数据处理程序,因此使用较大的本机 64 位变量可以通过处理 64 位数据块来实现更快的操作,而不是使 32 位数据块的数量增加一倍。 :) @BenPersonick - 您可以在 64 位处理器(例如 SHA256)上运行 256 位算法。当然可以在 32 位处理器上运行 64 位算法(MD5 比消费级 64 位 CPU 存在的时间要长得多,而且它是 128 位算法)。运行“原生大小”的算法会比原生大小的算法快:)【参考方案9】:

在任何情况下,您都应该完整读取每个文件(大小不匹配的情况除外),因此只需读取两个文件并逐块比较。

使用哈希只会增加 CPU 使用率,仅此而已。由于你不写任何东西,操作系统的缓存会有效地丢弃你读取的数据,所以,在 Linux 下,只需使用cmp tool

【讨论】:

【参考方案10】:

以下是从我的个人项目中查找重复文件以对图片进行排序的代码,该代码也可以删除重复项。根据我的经验,首先使用 CRC32 之类的快速散列算法,然后执行 MD5 或 SHA1 甚至更慢,并且没有任何改进,因为大多数具有相同大小的文件确实是重复的,因此从 CPU 时间的角度来看,运行两次散列更昂贵,这种方法可能不适用于所有类型的项目,但对于图像文件绝对正确。在这里,我只对具有相同大小的文件进行 MD5 或 SHA1 散列。

PS:它依赖于 Apache commons 编解码器来高效地生成哈希。

示例用法:new DuplicateFileFinder("MD5").findDuplicateFilesList(filesList);

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;

    import org.apache.commons.codec.digest.DigestUtils;

    /**
     * Finds the duplicate files using md5/sha1 hashing, which is used only for the sizes which are of same size.
     *  
     * @author HemantSingh
     *
     */
    public class DuplicateFileFinder 

        private HashProvider hashProvider;
        // Used only for logging purpose.
        private String hashingAlgo;

        public DuplicateFileFinder(String hashingAlgo) 
            this.hashingAlgo = hashingAlgo;
            if ("SHA1".equalsIgnoreCase(hashingAlgo)) 
                hashProvider = new Sha1HashProvider();
             else if ("MD5".equalsIgnoreCase(hashingAlgo)) 
                hashProvider = new Md5HashProvider();
             else 
                throw new RuntimeException("Unsupported hashing algorithm:" + hashingAlgo + " Please use either SHA1 or MD5.");
            
        

        /**
         * This API returns the list of duplicate files reference.
         * 
         * @param files
         *            - List of all the files which we need to check for duplicates.
         * @return It returns the list which contains list of duplicate files for
         *         e.g. if a file a.JPG have 3 copies then first element in the list
         *         will be list with three references of File reference.
         */
        public List<List<File>> findDuplicateFilesList(List<File> files) 
            // First create the map for the file size and file reference in the array list.
            Map<Long, List<File>> fileSizeMap = new HashMap<Long, List<File>>();
            List<Long> potDuplicateFilesSize = new ArrayList<Long>();

            for (Iterator<File> iterator = files.iterator(); iterator.hasNext();) 
                File file = (File) iterator.next();
                Long fileLength = new Long(file.length());
                List<File> filesOfSameLength = fileSizeMap.get(fileLength);
                if (filesOfSameLength == null) 
                    filesOfSameLength = new ArrayList<File>();
                    fileSizeMap.put(fileLength, filesOfSameLength);
                 else 
                    potDuplicateFilesSize.add(fileLength);
                
                filesOfSameLength.add(file);
            

            // If we don't have any potential duplicates then skip further processing.
            if (potDuplicateFilesSize.size() == 0) 
                return null;
            

            System.out.println(potDuplicateFilesSize.size() + " files will go thru " + hashingAlgo + " hash check to verify if they are duplicate.");

            // Now we will scan the potential duplicate files, and eliminate false positives using md5 hash check.
            List<List<File>> finalListOfDuplicates = new ArrayList<List<File>>();
            for (Iterator<Long> potDuplicatesFileSizeIterator = potDuplicateFilesSize
                    .iterator(); potDuplicatesFileSizeIterator.hasNext();) 
                Long fileSize = (Long) potDuplicatesFileSizeIterator.next();
                List<File> potDupFiles = fileSizeMap.get(fileSize);
                Map<String, List<File>> trueDuplicateFiles = new HashMap<String, List<File>>();
                for (Iterator<File> potDuplicateFilesIterator = potDupFiles.iterator(); potDuplicateFilesIterator
                        .hasNext();) 
                    File file = (File) potDuplicateFilesIterator.next();
                    try 
                        String md5Hex = hashProvider.getHashHex(file);
                        List<File> listOfDuplicatesOfAFile = trueDuplicateFiles.get(md5Hex);
                        if (listOfDuplicatesOfAFile == null) 
                            listOfDuplicatesOfAFile = new ArrayList<File>();
                            trueDuplicateFiles.put(md5Hex, listOfDuplicatesOfAFile);
                        
                        listOfDuplicatesOfAFile.add(file);
                     catch (IOException e) 
                        e.printStackTrace();
                    
                
                Collection<List<File>> dupsOfSameSizeList = trueDuplicateFiles.values();
                for (Iterator<List<File>> dupsOfSameSizeListIterator = dupsOfSameSizeList.iterator(); dupsOfSameSizeListIterator
                        .hasNext();) 
                    List<File> list = (List<File>) dupsOfSameSizeListIterator.next();
                    // It will be duplicate only if we have more then one copy of it.
                    if (list.size() > 1) 
                        finalListOfDuplicates.add(list);
                        System.out.println("Duplicate sets found: " + finalListOfDuplicates.size());
                    
                
            

            return finalListOfDuplicates;
        

        abstract class HashProvider 
            abstract String getHashHex(File file) throws IOException ;
        

        class Md5HashProvider extends HashProvider 
            String getHashHex(File file) throws IOException 
                return DigestUtils.md5Hex(new FileInputStream(file));
            
        
        class Sha1HashProvider extends HashProvider 
            String getHashHex(File file) throws IOException 
                return DigestUtils.sha1Hex(new FileInputStream(file));
            
        
    

【讨论】:

【参考方案11】:

我们在这里优化的是花费在任务上的时间。 不幸的是,我们对手头的任务知之甚少,无法知道最佳解决方案应该是什么。

是一次性比较2个任意文件吗? 然后比较大小,然后简单地逐字节(或逐字节)比较文件,如果这对您的 IO 更好。

如果是2组大文件,或者多组文件,不是一次性练习。但是会经常发生的事情,那么应该为每个文件存储哈希值。散列永远不是唯一的,但是具有 9 位数字(32 位)的散列对于大约 40 亿个组合来说是好的,而 64 位的数字足以区分一些 16 * 10^18 Quintillion 不同的文件.

一个不错的折衷方案是为每个文件生成 2 个 32 位哈希,一个用于前 8k,另一个用于 1MB+8k,将它们作为一个 64 位数字拼接在一起。将所有现有文件编入数据库应该相当快,并且针对该数据库查找候选文件也应该非常快。一旦匹配,确定它们是否相同的唯一方法是比较整个文件。

我坚信为人们提供他们需要的东西,但这并不总是他们认为自己需要或想要的东西。

【讨论】:

【参考方案12】:

我记得旧的调制解调器传输协议,如 Zmodem,会在发送每个块时对其进行某种 CRC 比较。 CRC32,如果我对古代历史的记忆足够好的话。我不建议您制定自己的传输协议,除非这正是您正在做的事情,但您可以让它定期抽查文件的一个块,或者对每个 8k 块进行哈希处理对处理器来处理。我自己没试过。

【讨论】:

以上是关于检查两个文件是不是相等的最快哈希算法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

hash算法

Java HashCode

Java中最快的字符串哈希算法

万能的进制哈希

Object Equals && GetHashCode

ASP.NET 成员使用的默认哈希算法是啥?