How to compare 2 files fast using .NET?

Typical approaches recommend reading the binary via FileStream and comparing it byte-by-byte.

  • Would a checksum comparison such as CRC be faster?
  • Are there any .NET libraries that can generate a checksum for a file?
222260 次浏览

校验和比较很可能比逐字节比较慢。

为了生成校验和,您需要加载文件的每个字节,并对其执行处理。然后必须在第二个文件中执行此操作。处理几乎肯定会比比较检查慢。

至于生成校验和: 可以通过加密类轻松实现。

但是,如果能够预先计算“测试”或“基本”情况的校验和,校验和可能会更快,也更有意义。如果您有一个现有的文件,并且您正在检查一个新文件是否与现有文件相同,那么预先计算“现有”文件上的校验和将意味着只需要在新文件上执行一次 DiskIO 操作。这可能比逐字节比较快。

编辑: 此方法将 没有工作比较二进制文件!

在.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));

如果文件不太大,你可以使用:

public static byte[] ComputeFileHash(string fileName)
{
using (var stream = File.OpenRead(fileName))
return System.Security.Cryptography.MD5.Create().ComputeHash(stream);
}

只有当散列对存储有用时,比较散列才是可行的。

(将代码编辑得更干净。)

除了 Reed Copsey的回答:

  • 最糟糕的情况是两个文件是相同的。在这种情况下,最好逐字节比较文件。

  • 如果这两个文件不一样,你可以通过更快地检测到它们不一样来加快速度。

例如,如果这两个文件的长度不同,那么您就知道它们不可能完全相同,您甚至不必比较它们的实际内容。

与逐字节比较相比,校验和比较可能稍微快一点的唯一原因是您一次读取一个文件,这在某种程度上减少了磁盘头的查找时间。然而,这种微小的收益很可能会被计算散列的额外时间所消耗掉。

当然,校验和比较只有在文件相同的情况下才有可能更快。如果不是,则逐字节比较将在第一个差异处结束,从而使其快得多。

您还应该考虑到,哈希代码比较只能告诉您,这些文件是相同的 很有可能。为了100% 确定,需要逐字节进行比较。

例如,如果哈希代码为32位,那么如果哈希代码匹配,则可以约99.9999998% 地确定文件是相同的。这是接近100% ,但如果你真的需要100% 的确定性,那就不是了。

最慢的方法是逐字节比较两个文件。我能够得到的最快的结果是类似的比较,但不是一次一个字节,而是使用大小为 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;
}

如果不读取8字节的小块,而是放置一个循环,读取更大的块,那么速度会更快。我把平均比较时间缩短到四分之一。

    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;
}
}
}
}
}

对具有相同长度的大文件的另一个改进可能是不按顺序读取文件,而是比较或多或少的随机块。

您可以使用多个线程,从文件中的不同位置开始并向前或向后比较。

通过这种方式,您可以在文件的中间/末尾检测到更改,比使用顺序方法更快地检测到更改。

我的实验表明,它肯定有助于调用流。ReadByte ()的次数更少,但是使用 BitConverter 打包字节与比较字节数组中的字节没有太大区别。

因此,我们可以用最简单的一个循环代替上面注释中的“数学. 天花板和迭代”循环:

            for (int i = 0; i < count1; i++)
{
if (buffer1[i] != buffer2[i])
return false;
}

我猜这是因为 BitConverter.ToInt64在进行比较之前需要做一些工作(检查参数,然后执行位移) ,最终的工作量相当于两个数组中比较8个字节的工作量。

如果您只需要比较两个文件,我想最快的方法是(在 C 中,我不知道它是否适用于。NET)

  1. 打开两个文件 f1,f2
  2. 得到相应的文件长度 l1,l2
  3. 如果 l1! = l2,则文件不同; 停止
  4. Mmap ()两个文件
  5. 在 mmap () ed 文件中使用 memcmp ()

OTOH,如果您需要查找一组 N 个文件中是否存在重复的文件,那么最快的方法无疑是使用哈希来避免 N 路逐位比较。

说实话,我觉得你应该尽可能地减少你的搜索树。

逐字节检查前需要检查的事项:

  1. 尺寸一样吗?
  2. 文件 A 中的最后一个字节与文件 B 不同

另外,一次读取大块会更有效,因为驱动器可以更快地读取顺序字节。一个字节一个字节地执行不仅会导致更多的系统调用,而且如果两个文件都在同一个驱动器上,还会导致传统硬盘的读取头更频繁地来回搜索。

将块 A 和块 B 读入一个字节缓冲区,并对它们进行比较(不要使用 Array)。等于,见注释)。调整代码块的大小,直到你觉得在内存和性能之间找到了一个很好的平衡点。您也可以多线程进行比较,但不要多线程磁盘读取。

如果你决定你真的需要一个 全字节逐字节比较(参见其他关于散列的讨论答案) ,那么最简单的解决方案是:


& bull; for‘ System. String’path name:
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)));

与其他一些公布的答案不同,这对于 任何类型的文件:二进制文件、文本、媒体、可执行文件等来说是绝对正确的,但是作为一个 全部 < em > 二进制 比较,那些在“不重要”方面与 只有不同的文件(例如 BOM结尾字符编码媒体元数据、空白、填充、源代码注释等)将总是被认为是 不平等

这段代码将两个文件完全加载到内存中,因此应该是 不能用于比较 < strong > 真正巨大的 文件。除了这个重要的警告之外,考虑到。NET GC(因为它的基本优化保持小,短命分配 非常便宜) ,事实上甚至可能是最佳的时候 预计文件大小将小于 < a href = “ https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap--net-4-5/”rel = “ nofollow noReferrer”> 85K ,因为使用最少的用户代码(如下所示)意味着最大限度地委托文件性能问题到 CLRBCLBCL0,以受益于(例如)最新的设计技术,系统代码和自适应运行时优化。

此外,对于这样的日常场景,关于通过 LINQ枚举器进行逐字节比较的性能(如下所示)的担忧是没有意义的,因为为了文件 I/O 而击中磁盘 一点也不将使各种内存比较替代数量级的好处相形见绌。例如,尽管 SequenceEqual 是的实际上为我们提供了 放弃第一次不匹配的“优化”,但是在已经获取了文件的内容(对于任何真正正面的情况,每个内容都是完全必要的)之后,这几乎无关紧要。< br > < br > < br > < hr > 1.一个鲜为人知的例外: NTFS 备用数据流不会由 < em > any 检查本页所讨论的答案,因此这些数据流对于其他报告为“相同”的文件可能是不同的

(希望)合理有效的事物:

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;
}
}

我认为有些应用程序中,“散列”比逐字节比较快。 如果您需要比较一个文件与其他人或有一个缩略图的照片,可以更改。 这取决于它在哪里使用以及如何使用。

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));

可选地,我们可以将散列保存在数据库中。

希望这个能帮上忙

下面是一些实用函数,它们允许您确定两个文件(或两个流)是否包含相同的数据。

我已经提供了一个“快速”版本,它是多线程的,因为它使用 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;
}

我发现这种方法很有效,首先比较不读取数据的长度,然后比较读取字节序列

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)));
}

还有一个来自@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;
}
}

我的答案是@lar 的派生物,但是修复了对 Stream.Read的调用中的 bug。我还添加了一些快速路径检查,其他答案,并输入验证。简而言之,这应该是 的答案:

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;
}
}
}
}
}
}

灵感来自 https://dev.to/emrahsungu/how-to-compare-two-files-using-net-really-really-fast-2pd9

下面是一个使用 AVX2 SIMD 指令的建议:

using System.Buffers;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;


namespace FileCompare;


public static class FastFileCompare
{
public static bool AreFilesEqual(FileInfo fileInfo1, FileInfo fileInfo2, int bufferSize = 4096 * 32)
{
if (fileInfo1.Exists == false)
{
throw new FileNotFoundException(nameof(fileInfo1), fileInfo1.FullName);
}


if (fileInfo2.Exists == false)
{
throw new FileNotFoundException(nameof(fileInfo2), fileInfo2.FullName);
}


if (fileInfo1.Length != fileInfo2.Length)
{
return false;
}


if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
 

using FileStream fileStream01 = fileInfo1.OpenRead();
using FileStream fileStream02 = fileInfo2.OpenRead();
ArrayPool<byte> sharedArrayPool = ArrayPool<byte>.Shared;
byte[] buffer1 = sharedArrayPool.Rent(bufferSize);
byte[] buffer2 = sharedArrayPool.Rent(bufferSize);
Array.Fill<byte>(buffer1, 0);
Array.Fill<byte>(buffer2, 0);
try
{
while (true)
{
int len1 = 0;
for (int read;
len1 < buffer1.Length &&
(read = fileStream01.Read(buffer1, len1, buffer1.Length - len1)) != 0;
len1 += read)
{
}


int len2 = 0;
for (int read;
len2 < buffer1.Length &&
(read = fileStream02.Read(buffer2, len2, buffer2.Length - len2)) != 0;
len2 += read)
{
}


if (len1 != len2)
{
return false;
}


if (len1 == 0)
{
return true;
}


unsafe
{
fixed (byte* pb1 = buffer1)
{
fixed (byte* pb2 = buffer2)
{
int vectorSize = Vector256<byte>.Count;
for (int processed = 0; processed < len1; processed += vectorSize)
{
Vector256<byte> result = Avx2.CompareEqual(Avx.LoadVector256(pb1 + processed), Avx.LoadVector256(pb2 + processed));
if (Avx2.MoveMask(result) != -1)
{
return false;
}
}
}
}
}
}
}
finally
{
sharedArrayPool.Return(buffer1);
sharedArrayPool.Return(buffer2);
}
}
}

不算是答案,但有点好笑。
下面是 github 的 CoPilot (AI)的建议: -)

public static void CompareFiles(FileInfo actualFile, FileInfo expectedFile) {
if (actualFile.Length != expectedFile.Length) {
throw new Exception($"File {actualFile.Name} has different length in actual and expected directories.");
}


// compare the files on a byte level
using var actualStream   = actualFile.OpenRead();
using var expectedStream = expectedFile.OpenRead();
var       actualBuffer   = new byte[1024];
var       expectedBuffer = new byte[1024];
int       actualBytesRead;
int       expectedBytesRead;
do {
actualBytesRead   = actualStream.Read(actualBuffer, 0, actualBuffer.Length);
expectedBytesRead = expectedStream.Read(expectedBuffer, 0, expectedBuffer.Length);
if (actualBytesRead != expectedBytesRead) {
throw new Exception($"File {actualFile.Name} has different content in actual and expected directories.");
}


if (!actualBuffer.SequenceEqual(expectedBuffer)) {
throw new Exception($"File {actualFile.Name} has different content in actual and expected directories.");
}
} while (actualBytesRead > 0);
}

我发现 SequenceEqual的用法特别有趣。