我对文件对比这一块还是比较感兴趣的,也想知道哪种方式性价比最高,效率最好,所以,根据这篇文章,我自己也自测一下,顺便留出自己对比的结果,供大佬们参考一二。
大致对比方案
我这边根据文章里的主要三个方案
- 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的方式是值得借鉴和学习的。
代码地址
https://github.com/kesshei/FileCompare.git
https://gitee.com/kesshei/FileCompare.git
阅
一键三连呦!,感谢大佬的支持,您的支持就是我的动力!