使用C#创建大文件的校验和的最快方法是什么?

144

我需要在几台机器之间同步大文件。这些文件的大小可以达到6GB。同步将手动进行,每隔几周进行一次。由于文件名可能随时更改,因此无法考虑文件名。

我的计划是在目标PC和源PC上创建校验和,然后复制所有具有校验和但尚未存在于目标位置的文件到目标位置。我的第一次尝试类似于:

using System.IO;
using System.Security.Cryptography;

private static string GetChecksum(string file)
{
    using (FileStream stream = File.OpenRead(file))
    {
        SHA256Managed sha = new SHA256Managed();
        byte[] checksum = sha.ComputeHash(stream);
        return BitConverter.ToString(checksum).Replace("-", String.Empty);
    }
}

问题出在运行时间:
- 对于一个1.6 GB的文件使用SHA256 -> 20分钟
- 对于一个1.6 GB的文件使用MD5 -> 6.15分钟

有没有更好、更快的方法来获取校验和(也许是使用更好的哈希函数)?


2
你真的需要检查校验和吗?你是如何复制文件的?如果你在使用Windows系统,我建议使用最新版本的Robocopy... - Mesh
6
如果两个候选文件的大小不同,可以采用一个好方法,只对它们进行哈希处理。https://dev59.com/4nVC5IYBdhLWcg3wcwwm#288756 - Matthew Lock
9个回答

131
问题在于SHA256Managed每次读取4096个字节(从FileStream继承并覆盖Read(byte[], int, int)以查看它从文件流中读取了多少),这对于磁盘IO来说是太小的缓冲区。

为了加快速度(使用SHA256在我的计算机上对2 GB文件进行哈希需要2分钟,MD5需要1分钟),请将FileStream包装在BufferedStream中,并设置适当大小的缓冲区大小(我尝试过大约为1 MB的缓冲区):

// Not sure if BufferedStream should be wrapped in using block
using(var stream = new BufferedStream(File.OpenRead(filePath), 1200000))
{
    // The rest remains the same
}

4
好的,这就是区别所在 - 使用 MD5 对 1.6GB 文件进行哈希,在我的计算机上花费了5.2秒(QuadCode @2.6 GHz, 8GB Ram),甚至比本地实现还要快... - crono
4
我不明白。我刚尝试了这个建议,但差别微乎其微。没有缓冲的1024mb文件需要12-14秒,使用缓冲也需要12-14秒 - 我知道读取数百个4k块会产生更多IO,但我想知道是否框架或框架下面的本机API已经处理了这个问题。 - Christian Casutt
19
有点晚了,但是对于FileStream而言,现在不再需要将流包装在BufferedStream中,因为它已经在FileStream自身中完成了。来源 - Reyhn
我正在处理一个小文件(<10MB),但计算MD5哈希值却需要很长时间。即使我使用了 .Net 4.5,但是使用 BufferedStream 方法可以将哈希时间从大约 8.6 秒缩短到不到 300 毫秒,适用于一个 8.6MB 的文件。 - Taegost
CRC32怎么样?我知道碰撞的机会可能会增加,但如果我们愿意忽略这个方面,它比MD5/SHA更快吗? - Sarthak Mittal
显示剩余2条评论

76

不要对整个文件进行校验和计算,而是每100MB创建一组校验和,使每个文件都有一个校验和集合。

然后在比较校验和时,可以在第一个不同的校验和之后停止比较,提前结束,避免处理整个文件。

对于相同的文件,仍需要花费完整的时间。


2
我喜欢这个想法,但在我的情况下它行不通,因为随着时间的推移,我会得到很多未更改的文件。 - crono
2
你如何对文件的每100MB进行校验和? - Smith
1
出于安全原因,使用校验和并排除某些字节并不是一个好主意,因为攻击者可以更改你已经排除的那些字节。 - b.kiener
3
当进行一对一比较时,+1 是一个绝佳的想法。不幸的是,我正在使用MD5哈希作为索引,在众多重复文件中查找唯一文件(多对多检查)。 - Nathan Goings
2
@b.kiener,没有任何字节被排除。您误解了他的意思。 - Soroush Falahati
显示剩余2条评论

56

正如Anton Gogolev所指出的那样, FileStream默认每次读取4096字节,但是您可以使用FileStream构造函数指定任何其他值:

new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 16 * 1024 * 1024)

请注意,Microsoft的Brad Abrams在2004年写道:

将BufferedStream包装在FileStream周围没有任何好处。我们大约4年前将BufferedStream的缓冲逻辑复制到FileStream中,以鼓励更好的默认性能。

source


23

调用Windows版本的md5sum.exe。在我使用1.2 GB文件时,它的速度大约是.NET实现的两倍。

public static string Md5SumByProcess(string file) {
    var p = new Process ();
    p.StartInfo.FileName = "md5sum.exe";
    p.StartInfo.Arguments = file;            
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.RedirectStandardOutput = true;
    p.Start();
    p.WaitForExit();           
    string output = p.StandardOutput.ReadToEnd();
    return output.Split(' ')[0].Substring(1).ToUpper ();
}

3
使用来自pc-tools.net/win32/md5sums的md5sums.exe可以使速度非常快。1681457152字节,8672毫秒= 184.91 MB / sec-> 1.6 GB约9秒这对于我的目的足够快了。 - crono

18

好的 - 感谢各位 - 让我总结一下:

  1. 使用一个“本地”exe文件进行哈希运算所需时间从6分钟减少到了10秒,提升非常明显。
  2. 增加缓冲区可以使速度更快,在.Net中使用MD5算法,1.6GB的文件只需5.2秒,因此我将采用这个解决方案 - 再次感谢。

10

我对缓冲区大小进行了测试,运行了这段代码。

using (var stream = new BufferedStream(File.OpenRead(file), bufferSize))
{
    SHA256Managed sha = new SHA256Managed();
    byte[] checksum = sha.ComputeHash(stream);
    return BitConverter.ToString(checksum).Replace("-", String.Empty).ToLower();
}

我测试了一个大小为29.5GB的文件,并得到以下结果:

  • 10,000: 369.24秒
  • 100,000: 362.55秒
  • 1,000,000: 361.53秒
  • 10,000,000: 434.15秒
  • 100,000,000: 435.15秒
  • 1,000,000,000: 434.31秒
  • 当使用原始的非缓冲代码时,结果为376.22秒。

我的计算机配置为i5 2500K CPU、12GB RAM和OCZ Vertex 4 256GB SSD硬盘。

接下来,我尝试使用一般的2TB硬盘进行测试,结果如下:

  • 10,000: 368.52秒
  • 100,000: 364.15秒
  • 1,000,000: 363.06秒
  • 10,000,000: 678.96秒
  • 100,000,000: 617.89秒
  • 1,000,000,000: 626.86秒
  • 当使用非缓冲代码时,结果为368.24秒。

因此,我建议使用无缓冲或最大1毫秒的缓冲。


1
我不明白。这个测试怎么会与Anton Gogolev的接受答案相矛盾呢? - buddybubble
你能为你的数据添加每个字段的描述吗? - videoguy

4

我知道我来晚了,但在实施方案之前进行了测试。

我对内置的MD5类和md5sum.exe进行了测试。在我的情况下,内置类每次运行需要13秒,而md5sum.exe需要16-18秒左右。

    DateTime current = DateTime.Now;
    string file = @"C:\text.iso";//It's 2.5 Gb file
    string output;
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(file))
        {
            byte[] checksum = md5.ComputeHash(stream);
            output = BitConverter.ToString(checksum).Replace("-", String.Empty).ToLower();
            Console.WriteLine("Total seconds : " + (DateTime.Now - current).TotalSeconds.ToString() + " " + output);
        }
    }

4

2
你做错了某些事情(可能是读取缓冲区太小)。在一台年代较久远的机器上(2002年的Athlon 2x1800MP),磁盘DMA可能不正常(当进行顺序读取时,6.6M/s非常慢):
创建一个带有“随机”数据的1G文件:
# dd if=/dev/sdb of=temp.dat bs=1M count=1024    
1073741824 bytes (1.1 GB) copied, 161.698 s, 6.6 MB/s

# time sha1sum -b temp.dat
abb88a0081f5db999d0701de2117d2cb21d192a2 *temp.dat

1m5.299s

# time md5sum -b temp.dat
9995e1c1a704f9c1eb6ca11e7ecb7276 *temp.dat

1m58.832s

这也很奇怪,对我来说md5始终比sha1慢(多次重新运行)。


是的 - 我会尝试增加缓存 - 就像Anton Gogolev建议的那样。我通过“本地”MD5.exe运行了一个1.6 GB文件,耗时9秒。 - crono

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接