如何使用 .NET 快速比较 2 个文件?
Posted
技术标签:
【中文标题】如何使用 .NET 快速比较 2 个文件?【英文标题】:How to compare 2 files fast using .NET? 【发布时间】:2010-11-24 10:00:09 【问题描述】:Typical approaches 建议通过 FileStream 读取二进制文件并逐字节比较。
CRC 等校验和比较会更快吗? 是否有任何 .NET 库可以为文件生成校验和?【问题讨论】:
欺骗:***.com/questions/211008/c-file-management 【参考方案1】:最慢的方法是逐字节比较两个文件。我能想到的最快的是类似的比较,但不是一次一个字节,而是使用大小为 Int64 的字节数组,然后比较结果数字。
这是我想出的:
const int BYTES_TO_READ = sizeof(Int64);
static bool FilesAreEqual(FileInfo first, FileInfo second)
if (first.Length != second.Length)
return false;
if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase))
return true;
int iterations = (int)Math.Ceiling((double)first.Length / BYTES_TO_READ);
using (FileStream fs1 = first.OpenRead())
using (FileStream fs2 = second.OpenRead())
byte[] one = new byte[BYTES_TO_READ];
byte[] two = new byte[BYTES_TO_READ];
for (int i = 0; i < iterations; i++)
fs1.Read(one, 0, BYTES_TO_READ);
fs2.Read(two, 0, BYTES_TO_READ);
if (BitConverter.ToInt64(one,0) != BitConverter.ToInt64(two,0))
return false;
return true;
在我的测试中,我能够看到这个比简单的 ReadByte() 方案的性能高出近 3:1。平均超过 1000 次运行,我在 1063 毫秒得到了这个方法,在 3031 毫秒得到了下面的方法(直接逐字节比较)。哈希总是以大约 865 毫秒的平均时间返回亚秒级。此测试使用约 100MB 的视频文件。
这是我使用的 ReadByte 和散列方法,用于比较目的:
static bool FilesAreEqual_OneByte(FileInfo first, FileInfo second)
if (first.Length != second.Length)
return false;
if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase))
return true;
using (FileStream fs1 = first.OpenRead())
using (FileStream fs2 = second.OpenRead())
for (int i = 0; i < first.Length; i++)
if (fs1.ReadByte() != fs2.ReadByte())
return false;
return true;
static bool FilesAreEqual_Hash(FileInfo first, FileInfo second)
byte[] firstHash = MD5.Create().ComputeHash(first.OpenRead());
byte[] secondHash = MD5.Create().ComputeHash(second.OpenRead());
for (int i=0; i<firstHash.Length; i++)
if (firstHash[i] != secondHash[i])
return false;
return true;
【讨论】:
你让我的生活更轻松。谢谢 @anindis:为了完整起见,您可能需要同时阅读 @Lars' answer 和 @RandomInsano's answer。很高兴它帮助了这么多年! :)FilesAreEqual_Hash
方法应该在两个文件流上都有一个 using
,就像 ReadByte
方法一样,否则它将挂在两个文件上。
请注意,FileStream.Read()
实际上读取的字节数可能少于请求的字节数。你应该改用StreamReader.ReadBlock()
。
在 Int64 版本中,当流长度不是 Int64 的倍数时,最后一次迭代是使用上一次迭代的填充比较未填充的字节(这也应该相等,所以没问题)。此外,如果流长度小于 sizeof(Int64) 则未填充字节为 0,因为 C# 初始化数组。 IMO,代码可能应该评论这些奇怪的东西。【参考方案2】:
校验和比较很可能比逐字节比较慢。
为了生成校验和,您需要加载文件的每个字节,并对其进行处理。然后,您必须在第二个文件上执行此操作。处理几乎肯定会比比较检查慢。
至于生成校验和:您可以使用密码学类轻松完成此操作。这是带有 C# 的short example of generating an MD5 checksum。
但是,如果您可以预先计算“测试”或“基本”案例的校验和,校验和可能会更快并且更有意义。如果您有一个现有文件,并且您正在检查新文件是否与现有文件相同,则预先计算“现有”文件的校验和意味着只需要执行一次 DiskIO,在新文件。这可能比逐字节比较要快。
【讨论】:
请务必考虑您的文件所在的位置。如果您将本地文件与世界另一端的备份进行比较(或通过带宽可怕的网络),您最好先散列并通过网络发送校验和,而不是发送字节流到比较。 @ReedCopsey:我遇到了类似的问题,因为我需要存储由应该包含大量重复的多个详细说明生成的输入/输出文件。我想使用预先计算的散列,但你认为我可以合理地假设如果 2 个(例如 MD5)散列相等,则 2 个文件相等并避免进一步的 byte-2-byte 比较?据我所知,MD5/SHA1 等冲突真的不太可能...... @digEmAll 碰撞几率很低 - 不过,您始终可以使用更强的哈希值 - 即:使用 SHA256 而不是 SHA1,这将进一步降低碰撞的可能性。 感谢您的回答 - 我刚刚进入 .net。我假设如果有人使用哈希码/校验和技术,那么主文件夹的哈希将永久存储在某个地方?出于好奇,您将如何为 WPF 应用程序存储它——您会怎么做? (我目前正在查看 xml、文本文件或数据库)。【参考方案3】:如果您 d̲o̲ 决定您确实需要完整的逐字节比较(有关哈希的讨论,请参阅其他答案),那么最简单的解决方案是:
• `System.String` 路径名:public static bool AreFileContentsEqual(String path1, String path2) =>
File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2));
• 对于 `System.IO.FileInfo` 实例:public static bool AreFileContentsEqual(FileInfo fi1, FileInfo fi2) =>
fi1.Length == fi2.Length &&
(fi1.Length == 0L || File.ReadAllBytes(fi1.FullName).SequenceEqual(
File.ReadAllBytes(fi2.FullName)));
与其他一些已发布的答案不同,这对于任何类型的文件都是正确的: 二进制文件、文本、媒体、可执行文件等,但作为完整的二进制比较,仅在“不重要”方面不同的文件(例如BOM,line-ending,character encoding,媒体元数据,空白,填充,源代码 cmets 等注 1)将始终被视为不等于。
此代码将两个文件完全加载到内存中,因此它不应该用于比较真正巨大的文件。除了这个重要的警告之外,考虑到 .NET GC 的设计(因为它从根本上优化以保持较小,short-lived 分配 extremely cheap),完全加载并不是真正的惩罚,事实上,当 文件大小预计小于85K,因为使用最少的用户代码(如此处所示)意味着最大限度地将文件性能问题委托给CLR
、BCL
和JIT
受益于(例如)最新的设计技术、系统代码和自适应运行时优化。
此外,对于这样的工作日场景,对通过LINQ
枚举器(如此处所示)进行逐字节比较的性能的担忧是没有实际意义的,因为为文件 I 敲击磁盘 a̲t̲ a̲l̲l̲ /O 将使各种内存比较替代方案的优势相形见绌。例如,即使SequenceEqual
确实实际上给了我们在第一次不匹配时放弃的“优化”,这在已经获取文件内容之后几乎没有关系,每个对于任何真正的阳性病例都是完全必要的。
1。一个不明显的例外:NTFS 备用数据流不会被本页讨论的任何答案检查,因此对于其他被认为“相同”的文件可能会有所不同。
【讨论】:
这个不适合大文件。不利于内存使用,因为它会在开始比较字节数组之前读取两个文件直到最后。这就是为什么我宁愿选择带缓冲区的流式阅读器。 @Krypto_47 我在我的回答文本中讨论了这些因素和适当的用途。【参考方案4】:除了Reed Copsey的回答:
最坏的情况是两个文件相同。在这种情况下,最好逐字节比较文件。
如果这两个文件不相同,您可以通过更快地检测到它们不相同来加快处理速度。
例如,如果两个文件的长度不同,那么您就知道它们不可能完全相同,您甚至不必比较它们的实际内容。
【讨论】:
完整:另一大收获是一旦第 1 个位置的字节不同就停止。 @Henk:我觉得这太明显了 :-) 添加这个很好。这对我来说很明显,所以我没有包含它,但值得一提。【参考方案5】:如果您不读取小的 8 字节块,而是放置一个循环,读取更大的块,它会变得更快。我将平均比较时间减少到 1/4。
public static bool FilesContentsAreEqual(FileInfo fileInfo1, FileInfo fileInfo2)
bool result;
if (fileInfo1.Length != fileInfo2.Length)
result = false;
else
using (var file1 = fileInfo1.OpenRead())
using (var file2 = fileInfo2.OpenRead())
result = StreamsContentsAreEqual(file1, file2);
return result;
private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2)
const int bufferSize = 1024 * sizeof(Int64);
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
while (true)
int count1 = stream1.Read(buffer1, 0, bufferSize);
int count2 = stream2.Read(buffer2, 0, bufferSize);
if (count1 != count2)
return false;
if (count1 == 0)
return true;
int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64));
for (int i = 0; i < iterations; i++)
if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64)))
return false;
【讨论】:
通常检查count1 != count2
不正确。由于各种原因,Stream.Read()
返回的数量可能少于您提供的数量。【参考方案6】:
编辑:此方法不适用于比较二进制文件!
在 .NET 4.0 中,File
类具有以下两个新方法:
public static IEnumerable<string> ReadLines(string path)
public static IEnumerable<string> ReadLines(string path, Encoding encoding)
这意味着你可以使用:
bool same = File.ReadLines(path1).SequenceEqual(File.ReadLines(path2));
【讨论】:
您不需要将这两个文件都存储在内存中吗? 请注意,File 还具有 ReadAllBytes 函数,它也可以使用 SequenceEquals,因此请改用它,因为它适用于所有文件。正如@RandomInsano 所说,它存储在内存中,所以虽然它可以用于小文件,但我会小心地将它用于大文件。 @DaedalusAlpha 它返回一个可枚举的,因此这些行将按需加载,而不是一直存储在内存中。另一方面,ReadAllBytes 确实将整个文件作为数组返回。【参考方案7】:唯一可能使校验和比较比逐字节比较稍快的是您一次读取一个文件的事实,这在一定程度上减少了磁盘磁头的寻道时间。然而,计算散列的额外时间很可能会消耗掉这个微小的收益。
此外,校验和比较当然只有在文件相同的情况下才有可能更快。如果不是,则逐字节比较将在第一个差异处结束,从而加快速度。
您还应该考虑到哈希码比较只会告诉您文件相同的可能性很可能。要 100% 确定,您需要逐字节比较。
例如,如果哈希码是 32 位,那么如果哈希码匹配,您大约 99.99999998% 可以确定文件是相同的。这接近 100%,但如果您真的需要 100% 的确定性,那就不是了。
【讨论】:
使用更大的哈希值,您可以将误报的几率大大低于计算机在进行测试时出错的几率。 我不同意哈希时间与寻道时间。您可以在单次磁头搜索期间进行很多的计算。如果文件匹配的几率很高,我会使用包含很多位的哈希。如果有合理的匹配机会,我会一次比较它们一个块,比如 1MB 块。 (选择一个 4k 平均划分的块大小,以确保您永远不会分割扇区。) 为了解释@Guffa 的数字99.99999998%,它来自计算1 - (1 / (2^32))
,这是任何单个文件都会有一些给定32 位哈希的概率。两个不同文件具有相同哈希的概率是相同的,因为第一个文件提供“给定”哈希值,我们只需要考虑另一个文件是否匹配该值。使用 64 位和 128 位散列的机会减少到 99.999999999999999994% 和 99.99999999999999999999999999999999999997%(分别),好像这对如此深不可测的数字很重要。
...事实上,对于大多数人来说,这些数字比“无限多的文件碰撞到相同的哈希码”这一假定简单的概念(尽管是真的)更难掌握这一事实可以解释为什么人类不合理地怀疑接受哈希相等。【参考方案8】:
老实说,我认为您需要尽可能地修剪搜索树。
逐字节检查之前需要检查的事项:
-
尺寸一样吗?
文件 A 中的最后一个字节与文件 B 不同
此外,一次读取大块会更有效,因为驱动器读取顺序字节的速度更快。逐字节进行不仅会导致更多的系统调用,而且如果两个文件在同一个驱动器上,它还会导致传统硬盘驱动器的读取头更频繁地来回寻找。
将块 A 和块 B 读入字节缓冲区,并比较它们(不要使用 Array.Equals,请参阅 cmets)。调整块的大小,直到你觉得在内存和性能之间取得了很好的平衡。您也可以多线程比较,但不要多线程磁盘读取。
【讨论】:
使用 Array.Equals 是个坏主意,因为它会比较整个数组。很可能,至少一个块读取不会填满整个数组。 为什么比较整个数组是个坏主意?为什么块读取不会填充数组?肯定有一个很好的调整点,但这就是你使用尺寸的原因。在单独的线程中进行比较的额外积分。 当你定义一个字节数组时,它会有一个固定的长度。 (例如 - var buffer = new byte[4096])当你从文件中读取一个块时,它可能会也可能不会返回完整的 4096 字节。例如,如果文件只有 3000 字节长。 啊,现在我明白了!好消息是读取会返回加载到数组中的字节数,所以如果数组不能被填充,就会有数据。由于我们正在测试相等性,旧的缓冲区数据无关紧要。文档:msdn.microsoft.com/en-us/library/9kstw824(v=vs.110).aspx 同样重要的是,我建议使用 Equals() 方法是个坏主意。在 Mono 中,它们会进行内存比较,因为元素在内存中是连续的。然而,微软并没有覆盖它,而是只做一个参考比较,这里总是错误的。【参考方案9】:我的答案是 @lars 的派生词,但修复了对 Stream.Read
的调用中的错误。我还添加了一些其他答案具有的快速路径检查和输入验证。简而言之,这应该是的答案:
using System;
using System.IO;
namespace ConsoleApp4
class Program
static void Main(string[] args)
var fi1 = new FileInfo(args[0]);
var fi2 = new FileInfo(args[1]);
Console.WriteLine(FilesContentsAreEqual(fi1, fi2));
public static bool FilesContentsAreEqual(FileInfo fileInfo1, FileInfo fileInfo2)
if (fileInfo1 == null)
throw new ArgumentNullException(nameof(fileInfo1));
if (fileInfo2 == null)
throw new ArgumentNullException(nameof(fileInfo2));
if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase))
return true;
if (fileInfo1.Length != fileInfo2.Length)
return false;
else
using (var file1 = fileInfo1.OpenRead())
using (var file2 = fileInfo2.OpenRead())
return StreamsContentsAreEqual(file1, file2);
private static int ReadFullBuffer(Stream stream, byte[] buffer)
int bytesRead = 0;
while (bytesRead < buffer.Length)
int read = stream.Read(buffer, bytesRead, buffer.Length - bytesRead);
if (read == 0)
// Reached end of stream.
return bytesRead;
bytesRead += read;
return bytesRead;
private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2)
const int bufferSize = 1024 * sizeof(Int64);
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
while (true)
int count1 = ReadFullBuffer(stream1, buffer1);
int count2 = ReadFullBuffer(stream2, buffer2);
if (count1 != count2)
return false;
if (count1 == 0)
return true;
int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64));
for (int i = 0; i < iterations; i++)
if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64)))
return false;
或者如果你想变得超级棒,你可以使用异步变体:
using System;
using System.IO;
using System.Threading.Tasks;
namespace ConsoleApp4
class Program
static void Main(string[] args)
var fi1 = new FileInfo(args[0]);
var fi2 = new FileInfo(args[1]);
Console.WriteLine(FilesContentsAreEqualAsync(fi1, fi2).GetAwaiter().GetResult());
public static async Task<bool> FilesContentsAreEqualAsync(FileInfo fileInfo1, FileInfo fileInfo2)
if (fileInfo1 == null)
throw new ArgumentNullException(nameof(fileInfo1));
if (fileInfo2 == null)
throw new ArgumentNullException(nameof(fileInfo2));
if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase))
return true;
if (fileInfo1.Length != fileInfo2.Length)
return false;
else
using (var file1 = fileInfo1.OpenRead())
using (var file2 = fileInfo2.OpenRead())
return await StreamsContentsAreEqualAsync(file1, file2).ConfigureAwait(false);
private static async Task<int> ReadFullBufferAsync(Stream stream, byte[] buffer)
int bytesRead = 0;
while (bytesRead < buffer.Length)
int read = await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead).ConfigureAwait(false);
if (read == 0)
// Reached end of stream.
return bytesRead;
bytesRead += read;
return bytesRead;
private static async Task<bool> StreamsContentsAreEqualAsync(Stream stream1, Stream stream2)
const int bufferSize = 1024 * sizeof(Int64);
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
while (true)
int count1 = await ReadFullBufferAsync(stream1, buffer1).ConfigureAwait(false);
int count2 = await ReadFullBufferAsync(stream2, buffer2).ConfigureAwait(false);
if (count1 != count2)
return false;
if (count1 == 0)
return true;
int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64));
for (int i = 0; i < iterations; i++)
if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64)))
return false;
【讨论】:
bitconverter 位是否会更好 ``` for (var i = 0; i 【参考方案10】:如果文件不是太大,可以使用:
public static byte[] ComputeFileHash(string fileName)
using (var stream = File.OpenRead(fileName))
return System.Security.Cryptography.MD5.Create().ComputeHash(stream);
只有当哈希值对存储有用时,才可以比较哈希值。
(将代码编辑得更简洁。)
【讨论】:
【参考方案11】:我的实验表明,调用 Stream.ReadByte() 的次数肯定会有所帮助,但使用 BitConverter 打包字节与比较字节数组中的字节相比并没有太大区别。
因此,可以将上面评论中的“Math.Ceiling and iterations”循环替换为最简单的循环:
for (int i = 0; i < count1; i++)
if (buffer1[i] != buffer2[i])
return false;
我想这与 BitConverter.ToInt64 在比较之前需要做一些工作(检查参数然后执行位移)有关,并且最终的工作量与比较 8 个字节的工作量相同在两个数组中。
【讨论】:
Array.Equals 深入系统,因此它可能比在 C# 中逐字节进行要快得多。我不能代表微软,但在内心深处,Mono 使用 C 的 memcpy() 命令来实现数组相等。没有比这更快的了。 @RandomInsano 猜你的意思是 memcmp(),而不是 memcpy()【参考方案12】:对长度相同的大文件的另一项改进可能是不按顺序读取文件,而是比较或多或少的随机块。
您可以使用多个线程,从文件中的不同位置开始并向前或向后比较。
通过这种方式,您可以检测到文件中间/末尾的更改,比使用顺序方法更快。
【讨论】:
磁盘抖动会导致这里出现问题吗? 物理磁盘驱动器是的,SSD 会处理这个。【参考方案13】:如果你只需要比较两个文件,我想最快的方法是(在C中,我不知道它是否适用于.NET)
-
打开两个文件 f1, f2
获取各自的文件长度l1、l2
如果 l1 != l2 文件不同;停止
mmap() 两个文件
对 mmap()ed 文件使用 memcmp()
OTOH,如果你需要在一组 N 个文件中查找是否有重复文件,那么最快的方法无疑是使用哈希来避免 N 路逐位比较。
【讨论】:
【参考方案14】:一些(希望)相当有效的东西:
public class FileCompare
public static bool FilesEqual(string fileName1, string fileName2)
return FilesEqual(new FileInfo(fileName1), new FileInfo(fileName2));
/// <summary>
///
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></param>
/// <param name="bufferSize">8kb seemed like a good default</param>
/// <returns></returns>
public static bool FilesEqual(FileInfo file1, FileInfo file2, int bufferSize = 8192)
if (!file1.Exists || !file2.Exists || file1.Length != file2.Length) return false;
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
using (var stream1 = file1.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
using (var stream2 = file2.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
while (true)
var bytesRead1 = stream1.Read(buffer1, 0, bufferSize);
var bytesRead2 = stream2.Read(buffer2, 0, bufferSize);
if (bytesRead1 != bytesRead2) return false;
if (bytesRead1 == 0) return true;
if (!ArraysEqual(buffer1, buffer2, bytesRead1)) return false;
/// <summary>
///
/// </summary>
/// <param name="array1"></param>
/// <param name="array2"></param>
/// <param name="bytesToCompare"> 0 means compare entire arrays</param>
/// <returns></returns>
public static bool ArraysEqual(byte[] array1, byte[] array2, int bytesToCompare = 0)
if (array1.Length != array2.Length) return false;
var length = (bytesToCompare == 0) ? array1.Length : bytesToCompare;
var tailIdx = length - length % sizeof(Int64);
//check in 8 byte chunks
for (var i = 0; i < tailIdx; i += sizeof(Int64))
if (BitConverter.ToInt64(array1, i) != BitConverter.ToInt64(array2, i)) return false;
//check the remainder of the array, always shorter than 8 bytes
for (var i = tailIdx; i < length; i++)
if (array1[i] != array2[i]) return false;
return true;
【讨论】:
【参考方案15】:以下是一些实用函数,可让您确定两个文件(或两个流)是否包含相同的数据。
我提供了一个“快速”版本,它是多线程的,因为它使用 Tasks 比较不同线程中的字节数组(每个缓冲区从每个文件中读取的内容填充)。
正如预期的那样,它更快(大约快 3 倍),但它消耗更多 CPU(因为它是多线程的)和更多内存(因为每个比较线程需要两个字节数组缓冲区)。
public static bool AreFilesIdenticalFast(string path1, string path2)
return AreFilesIdentical(path1, path2, AreStreamsIdenticalFast);
public static bool AreFilesIdentical(string path1, string path2)
return AreFilesIdentical(path1, path2, AreStreamsIdentical);
public static bool AreFilesIdentical(string path1, string path2, Func<Stream, Stream, bool> areStreamsIdentical)
if (path1 == null)
throw new ArgumentNullException(nameof(path1));
if (path2 == null)
throw new ArgumentNullException(nameof(path2));
if (areStreamsIdentical == null)
throw new ArgumentNullException(nameof(path2));
if (!File.Exists(path1) || !File.Exists(path2))
return false;
using (var thisFile = new FileStream(path1, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var valueFile = new FileStream(path2, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
if (valueFile.Length != thisFile.Length)
return false;
if (!areStreamsIdentical(thisFile, valueFile))
return false;
return true;
public static bool AreStreamsIdenticalFast(Stream stream1, Stream stream2)
if (stream1 == null)
throw new ArgumentNullException(nameof(stream1));
if (stream2 == null)
throw new ArgumentNullException(nameof(stream2));
const int bufsize = 80000; // 80000 is below LOH (85000)
var tasks = new List<Task<bool>>();
do
// consumes more memory (two buffers for each tasks)
var buffer1 = new byte[bufsize];
var buffer2 = new byte[bufsize];
int read1 = stream1.Read(buffer1, 0, buffer1.Length);
if (read1 == 0)
int read3 = stream2.Read(buffer2, 0, 1);
if (read3 != 0) // not eof
return false;
break;
// both stream read could return different counts
int read2 = 0;
do
int read3 = stream2.Read(buffer2, read2, read1 - read2);
if (read3 == 0)
return false;
read2 += read3;
while (read2 < read1);
// consumes more cpu
var task = Task.Run(() =>
return IsSame(buffer1, buffer2);
);
tasks.Add(task);
while (true);
Task.WaitAll(tasks.ToArray());
return !tasks.Any(t => !t.Result);
public static bool AreStreamsIdentical(Stream stream1, Stream stream2)
if (stream1 == null)
throw new ArgumentNullException(nameof(stream1));
if (stream2 == null)
throw new ArgumentNullException(nameof(stream2));
const int bufsize = 80000; // 80000 is below LOH (85000)
var buffer1 = new byte[bufsize];
var buffer2 = new byte[bufsize];
var tasks = new List<Task<bool>>();
do
int read1 = stream1.Read(buffer1, 0, buffer1.Length);
if (read1 == 0)
return stream2.Read(buffer2, 0, 1) == 0; // check not eof
// both stream read could return different counts
int read2 = 0;
do
int read3 = stream2.Read(buffer2, read2, read1 - read2);
if (read3 == 0)
return false;
read2 += read3;
while (read2 < read1);
if (!IsSame(buffer1, buffer2))
return false;
while (true);
public static bool IsSame(byte[] bytes1, byte[] bytes2)
if (bytes1 == null)
throw new ArgumentNullException(nameof(bytes1));
if (bytes2 == null)
throw new ArgumentNullException(nameof(bytes2));
if (bytes1.Length != bytes2.Length)
return false;
for (int i = 0; i < bytes1.Length; i++)
if (bytes1[i] != bytes2[i])
return false;
return true;
【讨论】:
【参考方案16】:我认为有些应用程序的“散列”比逐字节比较要快。 如果您需要将文件与其他文件进行比较或拥有可以更改的照片缩略图。 这取决于它在哪里以及如何使用。
private bool CompareFilesByte(string file1, string file2)
using (var fs1 = new FileStream(file1, FileMode.Open))
using (var fs2 = new FileStream(file2, FileMode.Open))
if (fs1.Length != fs2.Length) return false;
int b1, b2;
do
b1 = fs1.ReadByte();
b2 = fs2.ReadByte();
if (b1 != b2 || b1 < 0) return false;
while (b1 >= 0);
return true;
private string HashFile(string file)
using (var fs = new FileStream(file, FileMode.Open))
using (var reader = new BinaryReader(fs))
var hash = new SHA512CryptoServiceProvider();
hash.ComputeHash(reader.ReadBytes((int)file.Length));
return Convert.ToBase64String(hash.Hash);
private bool CompareFilesWithHash(string file1, string file2)
var str1 = HashFile(file1);
var str2 = HashFile(file2);
return str1 == str2;
在这里,你可以得到最快的。
var sw = new Stopwatch();
sw.Start();
var compare1 = CompareFilesWithHash(receiveLogPath, logPath);
sw.Stop();
Debug.WriteLine(string.Format("Compare using Hash 0", sw.ElapsedTicks));
sw.Reset();
sw.Start();
var compare2 = CompareFilesByte(receiveLogPath, logPath);
sw.Stop();
Debug.WriteLine(string.Format("Compare byte-byte 0", sw.ElapsedTicks));
我们可以选择将哈希值保存在数据库中。
希望对你有帮助
【讨论】:
【参考方案17】:我发现这很有效,首先比较长度而不读取数据,然后比较读取的字节序列
private static bool IsFileIdentical(string a, string b)
if (new FileInfo(a).Length != new FileInfo(b).Length) return false;
return (File.ReadAllBytes(a).SequenceEqual(File.ReadAllBytes(b)));
【讨论】:
【参考方案18】:另一个答案,来自@chsh。 MD5 与文件相同的使用和快捷方式,文件不存在和不同的长度:
/// <summary>
/// Performs an md5 on the content of both files and returns true if
/// they match
/// </summary>
/// <param name="file1">first file</param>
/// <param name="file2">second file</param>
/// <returns>true if the contents of the two files is the same, false otherwise</returns>
public static bool IsSameContent(string file1, string file2)
if (file1 == file2)
return true;
FileInfo file1Info = new FileInfo(file1);
FileInfo file2Info = new FileInfo(file2);
if (!file1Info.Exists && !file2Info.Exists)
return true;
if (!file1Info.Exists && file2Info.Exists)
return false;
if (file1Info.Exists && !file2Info.Exists)
return false;
if (file1Info.Length != file2Info.Length)
return false;
using (FileStream file1Stream = file1Info.OpenRead())
using (FileStream file2Stream = file2Info.OpenRead())
byte[] firstHash = MD5.Create().ComputeHash(file1Stream);
byte[] secondHash = MD5.Create().ComputeHash(file2Stream);
for (int i = 0; i < firstHash.Length; i++)
if (i>=secondHash.Length||firstHash[i] != secondHash[i])
return false;
return true;
【讨论】:
你说if (i>=secondHash.Length ...
在什么情况下两个MD5哈希长度不同?以上是关于如何使用 .NET 快速比较 2 个文件?的主要内容,如果未能解决你的问题,请参考以下文章