我对文件对比这一块还是比较感兴趣的,也想知道哪种方式性价比最高,效率最好,所以,根据这篇文章,我自己也自测一下,顺便留出自己对比的结果,供大佬们参考一二。
大致对比方案
我这边根据文章里的主要三个方案
- MD5
- 缓存长度读取比较
- 缓存长度读取(Span)比较
- Hash256
- CRC(最后补的)
新增了Hash256模式,原因是因为GitHub就是用Hash256来确定文件的唯一性的,所以,也想测试下它的性能到底如何。
又新增了CRC模式,后来才想起来的。
代码大致如下
挺简单的一个抽象工具类,方便增加相关的相同测试。
我也搜了OpenAi的建议和NewBing的建议,大致都是一样的建议。
根据建议和参考文章
抽象工具
public abstract class AFileCompare
{
public AFileCompare(string name)
{
this.Name = name;
}
public string Name { get; set; }
public bool Compare(string file1, string file2)
{
var result = false;
Stopwatch stopwatch = Stopwatch.StartNew();
if (Check(file1, file2))
{
result = CompareCore(file1, file2);
}
stopwatch.Stop();
TimeSpan = stopwatch.Elapsed;
return result;
}
public abstract bool CompareCore(string file1, string file2);
public TimeSpan TimeSpan { get; set; }
public bool Check(string file1, string file2)
{
if (file1 == file2)
{
return true;
}
if (new FileInfo(file1).Length == new FileInfo(file2).Length)
{
return true;
}
return false;
}
public override string ToString()
{
return $"{Name}耗时:{TimeSpan.TotalSeconds}秒";
}
}
MD5工具
public class MD5Compare : AFileCompare
{
public MD5Compare() : base("MD5 ")
{
}
public override bool CompareCore(string file1, string file2)
{
using (var md5 = MD5.Create())
{
byte[] one, two;
using (var fs1 = File.Open(file1, FileMode.Open))
{
// 以FileStream读取文件内容,计算HASH值
one = md5.ComputeHash(fs1);
}
using (var fs2 = File.Open(file2, FileMode.Open))
{
// 以FileStream读取文件内容,计算HASH值
two = md5.ComputeHash(fs2);
}
for (int i = 0; i < one.Length; i++)
{
if (one[i] != two[i])
{
return false;
}
}
return true;
}
}
}
Hash256
public class HashCompare : AFileCompare
{
public HashCompare() : base("Hash256")
{
}
public override bool CompareCore(string file1, string file2)
{
byte[] one, two;
using (SHA1 mySHA1 = SHA1.Create())
{
using (FileStream stream = File.OpenRead(file1))
{
one = mySHA1.ComputeHash(stream);
}
}
using (SHA1 mySHA1 = SHA1.Create())
{
using (FileStream stream = File.OpenRead(file2))
{
two = mySHA1.ComputeHash(stream);
}
}
for (int i = 0; i < one.Length; i++)
{
if (one[i] != two[i])
{
return false;
}
}
return true;
}
}
缓存长度读取比较
public class FileSizeCompare : AFileCompare
{
public FileSizeCompare() : base("FileSize_4096")
{
}
public override bool CompareCore(string file1, string file2)
{
using (FileStream fs1 = new FileStream(file1, FileMode.Open))
using (FileStream fs2 = new FileStream(file2, FileMode.Open))
{
byte[] buffer1 = new byte[4096];
byte[] buffer2 = new byte[4096];
int bytesRead1;
int bytesRead2;
do
{
bytesRead1 = fs1.Read(buffer1, 0, buffer1.Length);
bytesRead2 = fs2.Read(buffer2, 0, buffer2.Length);
if (bytesRead1 != bytesRead2)
{
return false;
}
for (int i = 0; i < bytesRead1; i++)
{
if (buffer1[i] != buffer2[i])
{
return false;
}
}
} while (bytesRead1 > 0);
return true;
}
}
}
缓存长度读取(Span)比较
public class FileSizeCompare2 : AFileCompare
{
public FileSizeCompare2() : base("FileSize_4096_Span")
{
}
public override bool CompareCore(string file1, string file2)
{
using (FileStream fs1 = new FileStream(file1, FileMode.Open))
using (FileStream fs2 = new FileStream(file2, FileMode.Open))
{
byte[] buffer1 = new byte[4096];
byte[] buffer2 = new byte[4096];
int bytesRead1;
int bytesRead2;
do
{
bytesRead1 = fs1.Read(buffer1, 0, buffer1.Length);
bytesRead2 = fs2.Read(buffer2, 0, buffer2.Length);
if (bytesRead1 != bytesRead2)
{
return false;
}
if (!((ReadOnlySpan<byte>)buffer1).SequenceEqual((ReadOnlySpan<byte>)buffer2))
{
return false;
}
} while (bytesRead1 > 0);
return true;
}
}
}
CRC(补充)
public class CRCCompare : AFileCompare
{
public CRCCompare() : base("CRC")
{
}
public override bool CompareCore(string file1, string file2)
{
Crc32Algorithm crc32 = new Crc32Algorithm();
byte[] one, two;
using (var fs1 = File.Open(file1, FileMode.Open))
{
one = crc32.ComputeHash(fs1);
}
using (var fs2 = File.Open(file2, FileMode.Open))
{
two = crc32.ComputeHash(fs2);
}
for (int i = 0; i < one.Length; i++)
{
if (one[i] != two[i])
{
return false;
}
}
return true;
}
}
main 入口
static void Main(string[] args)
{
var files = new List<(string name, string source, string target)>()
{
("ubuntu_4.58 GB",@"E:\重装系统\ubuntu-22.04.2-desktop-amd64.iso",@"E:\重装系统\ubuntu-22.04.2-desktop-amd64 - 副本.iso"),
("Docker_523 MB",@"E:\重装系统\Docker Desktop Installer(1).exe",@"E:\重装系统\Docker Desktop Installer(1) - 副本.exe"),
("Postman_116 MB",@"E:\重装系统\Postman-win64-8.5.0-Setup.exe",@"E:\重装系统\Postman-win64-8.5.0-Setup - 副本.exe")
};
var list = new List<AFileCompare>();
list.Add(new MD5Compare());
list.Add(new HashCompare());
list.Add(new FileSizeCompare());
list.Add(new FileSizeCompare2());
foreach ((string name, string source, string target) in files)
{
foreach (var item in list)
{
var result = item.Compare(source, target);
Console.WriteLine($"{name} - {item.Name} 结果:{result} {item}");
}
Console.WriteLine();
}
Console.WriteLine("测试完毕");
Console.ReadLine();
}
测试结果如下
主要是对3个文件,4.5G,500M,100M文件进行了测试,大概也能看出来点结果了。
测试1
测试2
测试3
CRC补充三次
第一次
第二次
第三次
结果对比
根据我分析的表大致可以看出来,五种方式,根据各种不同的文件对比方式,在文件大小不一样的情况下,差别还是挺大的,特别是在大文件的情况下。
文件越大,反而MD5和Hash效果会更好。
文件小于1G ?,FileSize_Span方式会更优,除了MD5,其他方式基本都差不多,CRC属于不上也不下。
虽然,环境的因素会有一定的影响,假设我这个就是客户的机器,那可能性也是存在的。
所以,在同一台机器上的多次测试也有存在的合理性。
那么,如果对性能很在意的话,可以根据文件大小与各种对比方法之间的关系,找到一个函数映射关系,从而找到一个最优的对比方法出来。
总结
还挺意外的,本来想验证这个Span方式为何快,却看到了另外问题点,出乎意料。
另外,也看到了Github采用Hash256在获取指纹上,速度还是相对稳定的。那么,用它来对比文件还是提取指纹应该是最佳之选(性能要求苛刻的,得自己搞模型求最佳函数映射关系)
还有其他的细节优化还没有验证,但是Span的方式是值得借鉴和学习的。
《.NET 下最快比较两个文件内容是否相同》参考地址
https://www.cnblogs.com/waku/p/11069214.html
代码地址
https://github.com/kesshei/FileCompare.git
https://gitee.com/kesshei/FileCompare.git
阅
一键三连呦!,感谢大佬的支持,您的支持就是我的动力!