.NET CORE下最快比较两个文件内容是否相同的方法 (2)

我想到了LINQ中有一个比较序列的方法SequenceEqual,我们尝试使用该方法比较:

/// <summary> /// 读入到字节数组中比较(使用LINQ的SequenceEqual比较) /// </summary> /// <param></param> /// <param></param> /// <returns></returns> private static bool CompareBySequenceEqual(string file1, string file2) { const int BYTES_TO_READ = 1024 * 10; using (FileStream fs1 = File.Open(file1, FileMode.Open)) using (FileStream fs2 = File.Open(file2, FileMode.Open)) { byte[] one = new byte[BYTES_TO_READ]; byte[] two = new byte[BYTES_TO_READ]; while (true) { int len1 = fs1.Read(one, 0, BYTES_TO_READ); int len2 = fs2.Read(two, 0, BYTES_TO_READ); if (!one.SequenceEqual(two)) return false; if (len1 == 0 || len2 == 0) break; // 两个文件都读取到了末尾,退出while循环 } } return true; }

结果:

Method: CompareBySequenceEqual, Identical: True. Elapsed: 00:00:08.2174360

竟然比前两个都要慢(实际这也是所有方案中最慢的一个),LINQ的SequenceEqual看来不是为了效率而生.

那么我们不用那些花哨的功能,回归质朴,老实儿的使用while循环比较字节数组怎么样呢?

/// <summary> /// 读入到字节数组中比较(while循环比较字节数组) /// </summary> /// <param></param> /// <param></param> /// <returns></returns> private static bool CompareByByteArry(string file1, string file2) { const int BYTES_TO_READ = 1024 * 10; using (FileStream fs1 = File.Open(file1, FileMode.Open)) using (FileStream fs2 = File.Open(file2, FileMode.Open)) { byte[] one = new byte[BYTES_TO_READ]; byte[] two = new byte[BYTES_TO_READ]; while (true) { int len1 = fs1.Read(one, 0, BYTES_TO_READ); int len2 = fs2.Read(two, 0, BYTES_TO_READ); int index = 0; while (index < len1 && index < len2) { if (one[index] != two[index]) return false; index++; } if (len1 == 0 || len2 == 0) break; } } return true; }

结果是....

Method: CompareByByteArry, Identical: True. Elapsed: 00:00:01.5356821

1.53秒!大突破!看来有时候看起来笨拙的方法反而效果更好!

试验到此,比较两个900多MB的文件耗时1.5秒左右,读者对于该方法是否满意呢?

No!我不满意!我相信通过努力,一定会找到更快的方法的!

同样.NET CORE也在为了编写高性能代码而不断的优化中.

那么,我们如何继承优化我们的代码呢?

我突然想到在C# 7.2中加入的一个新的值类型: Span<T>,它用来代表一段连续的内存区域,并提供一系列可操作该该区域的方法.

对于我们的需求,因为我们不会更改数组的值,所以可以使用另外一个只读的类型ReadOnlySpan<T>追求更高的效率.

修改代码,使用ReadOnlySpan<T>:

/// <summary> /// 读入到字节数组中比较(ReadOnlySpan) /// </summary> /// <param></param> /// <param></param> /// <returns></returns> private static bool CompareByReadOnlySpan(string file1, string file2) { const int BYTES_TO_READ = 1024 * 10; using (FileStream fs1 = File.Open(file1, FileMode.Open)) using (FileStream fs2 = File.Open(file2, FileMode.Open)) { byte[] one = new byte[BYTES_TO_READ]; byte[] two = new byte[BYTES_TO_READ]; while (true) { int len1 = fs1.Read(one, 0, BYTES_TO_READ); int len2 = fs2.Read(two, 0, BYTES_TO_READ); // 字节数组可直接转换为ReadOnlySpan if (!((ReadOnlySpan<byte>)one).SequenceEqual((ReadOnlySpan<byte>)two)) return false; if (len1 == 0 || len2 == 0) break; // 两个文件都读取到了末尾,退出while循环 } } return true; }

核心为是用来比较的SequenceEqual方法,该方法是ReadOnlySpan的一个扩展方法,要注意它只是方法名与LINQ中一样,实现完全不同.
那么该方法的表现如何呢?

Method: CompareByReadOnlySpan, Identical: True. Elapsed: 00:00:00.9287703

不 到 一 秒!

相对上一个已经不错的结果,速度提高了差不多40%!

对此结果,我个人觉得已经很满意了,如果各位有更快的方法,请不吝赐教,我非常欢迎!

关于Span<T>结构类型,各位读者如有兴趣,可浏览该文章,该文有非常详细的介绍.

后记

文中的代码只是出于实验性质,实际应用中仍可以继续细节上的优化, 如:

如两个文件大小不同,直接返回false

如果两个文件路径相同,直接返回true

...

试验工程的Main方法源码:

static void Main(string[] args) { string file1 = @"C:\Users\WAKU\Desktop\file1.ISO"; string file2 = @"C:\Users\WAKU\Desktop\file2.ISO"; var methods = new Func<string, string, bool>[] { CompareByMD5, CompareByToInt64, CompareByByteArry, CompareByReadOnlySpan }; foreach (var method in methods) { var sw = Stopwatch.StartNew(); bool identical = method(file1, file2); Console.WriteLine("Method: {0}, Identical: {1}. Elapsed: {2}", method.Method.Name, identical, sw.Elapsed); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfgwp.html