怎么在.NET CORE中比較兩個文件內(nèi)容是否相同?相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
站在用戶的角度思考問題,與客戶深入溝通,找到澄海網(wǎng)站設(shè)計與澄海網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:成都網(wǎng)站制作、網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、域名注冊、虛擬主機、企業(yè)郵箱。業(yè)務(wù)覆蓋澄海地區(qū)。////// MD5 /// /// /// ///private static bool CompareByMD5(string file1, string file2) { // 使用.NET內(nèi)置的MD5庫 using (var md5 = MD5.Create()) { byte[] one, two; using (var fs1 = File.Open(file1, FileMode.Open)) { // 以FileStream讀取文件內(nèi)容,計算HASH值 one = md5.ComputeHash(fs1); } using (var fs2 = File.Open(file2, FileMode.Open)) { // 以FileStream讀取文件內(nèi)容,計算HASH值 two = md5.ComputeHash(fs2); } // 將MD5結(jié)果(字節(jié)數(shù)組)轉(zhuǎn)換成字符串進(jìn)行比較 return BitConverter.ToString(one) == BitConverter.ToString(two); } }
比較結(jié)果:
Method: CompareByMD5, Identical: True. Elapsed: 00:00:05.7933178
耗時5.79秒,感覺還不錯.然而,這是很好的解決方案嗎?
其實我們仔細(xì)想一下,答案應(yīng)該是否定的.
因為任何哈希算法本質(zhì)上都是對字節(jié)進(jìn)行一定的計算,而計算過程是要消耗時間的.
很多下載網(wǎng)站上提供了下載文件的哈希值,那是因為下載的源文件本身不會改變,只需要計算一次源文件的哈希值,提供給用戶驗證即可.
而我們的需求中,兩個文件都是不固定的,那么每次都要計算兩個文件的哈希值,就不太合適了.
所以,哈希比較這個方案被PASS.
這種求算法最優(yōu)解的問題,我以往的經(jīng)驗是: 去stackoverflow查找 :)
經(jīng)過我的艱苦努力,找到了一個非常切題的答案: How to compare 2 files fast using .NET?
得贊最多一個答案,將代碼改造了一下放入工程中:
////// /tupian/20230522/1359947#1359947 /// /// /// ///private static bool CompareByToInt64(string file1, string file2) { const int BYTES_TO_READ = sizeof(Int64); // 每次讀取8個字節(jié) int iterations = (int)Math.Ceiling((double)new FileInfo(file1).Length / BYTES_TO_READ); // 計算讀取次數(shù) 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]; for (int i = 0; i < iterations; i++) { // 循環(huán)讀取到字節(jié)數(shù)組中 fs1.Read(one, 0, BYTES_TO_READ); fs2.Read(two, 0, BYTES_TO_READ); // 轉(zhuǎn)換為Int64進(jìn)行數(shù)值比較 if (BitConverter.ToInt64(one, 0) != BitConverter.ToInt64(two, 0)) return false; } } return true; }
該方法基本的原理是循環(huán)讀取兩個文件,每次讀取8個字節(jié),轉(zhuǎn)換為Int64,再進(jìn)行數(shù)值比較.那么效率如何呢?
Method: CompareByToInt64, Identical: True. Elapsed: 00:00:08.0918099
什么?8秒!竟然比MD5還慢?這不是SO得贊最多的答案嗎,怎么會這樣?
其實分析一下不難想到原因,因為每次只讀取8個字節(jié),程序頻繁的進(jìn)行IO操作,導(dǎo)致性能低下.看來SO上的答案也不能迷信啊!
那么優(yōu)化的方向就變?yōu)榱巳绾螠p少IO操作帶來的損耗.
既然每次8個字節(jié)太少了,我們定義一個大一些的字節(jié)數(shù)組,比如1024個字節(jié).每次讀取1024個字節(jié)到數(shù)組中,然后進(jìn)行字節(jié)數(shù)組的比較.
但是這樣又帶來一個新問題,就是如何快速比較兩個字節(jié)數(shù)組是否相同?
我首先想到的是在MD5方法中用過的----將字節(jié)數(shù)組轉(zhuǎn)換成字符串進(jìn)行比較:
////// 讀入到字節(jié)數(shù)組中比較(轉(zhuǎn)為String比較) /// /// /// ///private static bool CompareByString(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 (BitConverter.ToString(one) != BitConverter.ToString(two)) return false; if (len1 == 0 || len2 == 0) break; // 兩個文件都讀取到了末尾,退出while循環(huán) } } return true; }
結(jié)果:
Method: CompareByString, Identical: True. Elapsed: 00:00:07.8088732
耗時也接近8秒,比上一個方法強不了多少.
分析一下原因,在每次循環(huán)中,字符串的轉(zhuǎn)換是一個非常耗時的操作.那么有沒有不進(jìn)行類型轉(zhuǎn)換的字節(jié)數(shù)組比較方法呢?
我想到了LINQ中有一個比較序列的方法SequenceEqual,我們嘗試使用該方法比較:
////// 讀入到字節(jié)數(shù)組中比較(使用LINQ的SequenceEqual比較) /// /// /// ///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循環(huán) } } return true; }
結(jié)果:
Method: CompareBySequenceEqual, Identical: True. Elapsed: 00:00:08.2174360
竟然比前兩個都要慢(實際這也是所有方案中最慢的一個),LINQ的SequenceEqual看來不是為了效率而生.
那么我們不用那些花哨的功能,回歸質(zhì)樸,老實兒的使用while循環(huán)比較字節(jié)數(shù)組怎么樣呢?
////// 讀入到字節(jié)數(shù)組中比較(while循環(huán)比較字節(jié)數(shù)組) /// /// /// ///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; }
結(jié)果是....
Method: CompareByByteArry, Identical: True. Elapsed: 00:00:01.5356821
1.53秒!大突破!看來有時候看起來笨拙的方法反而效果更好!
試驗到此,比較兩個900多MB的文件耗時1.5秒左右,讀者對于該方法是否滿意呢?
No!我不滿意!我相信通過努力,一定會找到更快的方法的!
同樣.NET CORE也在為了編寫高性能代碼而不斷的優(yōu)化中.
那么,我們?nèi)绾卫^續(xù)優(yōu)化我們的代碼呢?
我突然想到在C# 7.2中加入的一個新的值類型: Span
對于我們的需求,因為我們不會更改數(shù)組的值,所以可以使用另外一個只讀的類型ReadOnlySpan
修改代碼,使用ReadOnlySpan
////// 讀入到字節(jié)數(shù)組中比較(ReadOnlySpan) /// /// /// ///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); // 字節(jié)數(shù)組可直接轉(zhuǎn)換為ReadOnlySpan if (!((ReadOnlySpan )one).SequenceEqual((ReadOnlySpan )two)) return false; if (len1 == 0 || len2 == 0) break; // 兩個文件都讀取到了末尾,退出while循環(huán) } } return true; }
核心是用來比較的SequenceEqual方法,該方法是ReadOnlySpan的一個擴展方法,要注意它只是方法名與LINQ中一樣,實現(xiàn)完全不同.
那么該方法的表現(xiàn)如何呢?
Method: CompareByReadOnlySpan, Identical: True. Elapsed: 00:00:00.9287703
不 到 一 秒!
相對上一個已經(jīng)不錯的結(jié)果,速度提高了差不多40%!
對此結(jié)果,我個人覺得已經(jīng)很滿意了,如果各位有更快的方法,請不吝賜教,我非常歡迎!
關(guān)于Span
后記
文中的代碼只是出于實驗性質(zhì),實際應(yīng)用中仍可以繼續(xù)細(xì)節(jié)上的優(yōu)化, 如:
如兩個文件大小不同,直接返回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[] { 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); } }
看完上述內(nèi)容,你們掌握怎么在.NET CORE中比較兩個文件內(nèi)容是否相同的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!