Typical approaches建议通过FileStream读取二进制文件并逐字节地比较它。
答案 0 :(得分:119)
最慢的方法是逐字节比较两个文件。我能够提出的最快速度是类似的比较,但是你不是一次只使用一个字节,而是使用一个大小为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次运行,我在1063ms获得此方法,并且在3031ms处获得下面的方法(直接逐字节比较)。哈希总是在平均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;
}
答案 1 :(得分:105)
校验和比较很可能比逐字节比较慢。
为了生成校验和,您需要加载文件的每个字节,并对其执行处理。然后,您必须在第二个文件上执行此操作。处理几乎肯定比比较检查慢。
至于生成校验和:您可以使用加密类轻松完成此操作。这是一个带有C#的short example of generating an MD5 checksum。
但是,如果您可以预先计算“测试”或“基础”案例的校验和,则校验和可能更快并且更有意义。如果您有一个现有文件,并且您正在检查新文件是否与现有文件相同,那么预先计算“现有”文件上的校验和意味着只需要执行一次DiskIO,新文件。这可能比逐字节比较更快。
答案 2 :(得分:36)
如果您 决定您确实需要完整的逐字节比较(请参阅其他答案以讨论散列),那么单行解决方案是:
bool filesAreEqual = File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2));
与其他一些已发布的答案不同,这适用于任何类型的文件:二进制文本,文本,媒体,可执行文件等,但作为完整的二进制文件比较,以“不重要”方式仅的文件(例如BOM,line-ending,character encoding,媒体元数据,空白,填充,源代码注释等)将始终被视为不等于。
此代码完全将两个文件加载到内存中,因此不应将其用于比较 gigantic 文件。除了这种考虑之外,满载并不是真正的惩罚;实际上,这可能是文件大小的最佳.NET解决方案,预计小于85K,因为.NET
中的小分配非常便宜,并且上述代码最大程度地将文件性能和优化委托给CLR
/ BCL
。
此外,对于此类工作日场景,关于通过LINQ
枚举器(如此处所示)进行逐字节比较的性能的问题没有实际意义,因为在所有处命中磁盘文件I / O将使各种内存比较替代方案的好处相形见绌,几个数量级。例如,即使SequenceEqual
确实给我们放弃第一次不匹配的“优化”,这在获取文件内容后几乎不重要,每个完全有必要确认比赛..
另一方面,上面的代码 不包含不同大小的文件的急切中止,可以提供有形的(可能)可衡量的)性能差异。这个是有形的,因为虽然文件长度在WIN32_FILE_ATTRIBUTE_DATA
结构中可用(无论如何都必须首先获取任何文件访问),继续访问文件的内容需要完全不同的提取,这可能是可能避免的。如果您对此感到担忧,解决方案将变为两行:
// slight optimization over the code shown above
bool filesAreEqual = new FileInfo(path1).Length == new FileInfo(path2).Length &&
File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2));
如果(等效的)Length
值都被发现为零(未显示)和/或避免构建每个FileInfo
两次(也不是),您也可以对此进行扩展以避免二次提取示出)。
答案 3 :(得分:33)
除了 Reed Copsey 的回答:
最糟糕的情况是两个文件完全相同。在这种情况下,最好逐个字节地比较文件。
如果这两个文件不相同,您可以通过更快地检测到它们不相同来加快速度。
例如,如果这两个文件的长度不同,那么您就知道它们不能相同,甚至不必比较它们的实际内容。
答案 4 :(得分:15)
唯一可能使校验和比较比逐字节比较稍快的事情是你一次读取一个文件,这有点减少了磁头的搜索时间。然而,通过计算散列的额外时间可以很好地消除这种微小的增益。
此外,校验和比较当然只有在文件相同时才有可能更快。如果不是这样,逐字节比较将以第一个差异结束,使其快得多。
您还应该考虑哈希码比较仅告诉您很可能文件是相同的。要100%确定,您需要进行逐字节比较。
如果哈希码例如是32位,那么如果哈希码匹配,则大约99.99999998%确定文件是相同的。这接近100%,但如果你真的需要100%的确定性,那不是它。
答案 5 :(得分:15)
如果你不读取小的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;
}
}
}
}
}
答案 6 :(得分:11)
编辑:此方法 无法比较二进制文件!
在.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));
答案 7 :(得分:6)
老实说,我认为你需要尽可能地修剪你的搜索树。
逐字节前要检查的事项:
此外,由于驱动器更快地读取顺序字节,因此一次读取大块将更有效。逐字节转换不仅会导致更多的系统调用,而且如果两个文件位于同一驱动器上,它会导致传统硬盘驱动器的读取头更频繁地来回搜索。
将块A和块B读入字节缓冲区,并进行比较(不要使用Array.Equals,请参阅注释)。调整块的大小,直到达到你认为在内存和性能之间的良好折衷。您也可以对比较进行多线程处理,但不要对磁盘读取进行多线程处理。
答案 8 :(得分:2)
我的答案是@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;
}
}
}
}
}
}
答案 9 :(得分:2)
我的实验表明,调用Stream.ReadByte()的次数肯定会有所帮助,但使用BitConverter封装字节对于比较字节数组中的字节没有多大区别。
因此可以用最简单的一个替换上面评论中的“Math.Ceiling and iterations”循环:
for (int i = 0; i < count1; i++)
{
if (buffer1[i] != buffer2[i])
return false;
}
我想这与BitConverter.ToInt64在比较之前需要做一些工作(检查参数然后执行位移)的事实有关,并且最终与比较8字节的工作量相同在两个阵列中。
答案 10 :(得分:1)
如果您只需要比较两个文件,我想最快的方式是(在C中,我不知道它是否适用于.NET)
OTOH,如果您需要查找一组N个文件中是否存在重复文件,那么最快的方法无疑是使用散列来避免N路逐位比较。
答案 11 :(得分:1)
对长度相同的大文件的另一个改进可能是不按顺序读取文件,而是比较或多或少的随机块。
您可以使用多个线程,从文件中的不同位置开始并向前或向后比较。
通过这种方式,您可以检测文件中间/末尾的更改,速度比使用顺序方法更快。
答案 12 :(得分:1)
某事(希望)合理有效:
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;
}
}
答案 13 :(得分:1)
以下是一些实用程序函数,可用于确定两个文件(或两个流)是否包含相同的数据。
我提供了一个&#34;快速&#34;多线程的版本,因为它使用Tasks比较不同线程中的字节数组(每个缓冲区中已经读取的每个缓冲区)。
正如预期的那样,它的速度要快得多(大约快3倍),但它会消耗更多的CPU(因为它的多线程)和更多的内存(因为每个比较线程需要两个字节的数组缓冲区)。 / p>
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;
}
答案 14 :(得分:1)
如果文件不是太大,您可以使用:
public static byte[] ComputeFileHash(string fileName)
{
using (var stream = File.OpenRead(fileName))
return System.Security.Cryptography.MD5.Create().ComputeHash(stream);
}
如果散列对存储很有用,那么比较散列是可行的。
(将代码编辑为更干净的东西。)
答案 15 :(得分:0)
我认为应用程序中有#34; hash&#34;比逐字节比较要快。 如果您需要将文件与其他人进行比较,或者拥有可以更改的照片缩略图。 这取决于它在何处以及如何使用。
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));
可选地,我们可以将哈希保存在数据库中。
希望这可以提供帮助
答案 16 :(得分:0)
另一个答案,源于@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;
}
}
答案 17 :(得分:-1)
我发现这种方法很好地比较了首先读取数据的长度,然后比较了读取的字节序列
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)));
}