LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

【C#】.NET 下最快比較兩個文件內(nèi)容是否相同


2025年6月26日 16:49 本文熱度 47
最近項(xiàng)目有個需求,需要比較兩個任意大小文件的內(nèi)容是否相同,要求如下:


  1. 項(xiàng)目是.NET Core,所以使用C#進(jìn)行編寫比較方法


  2. 文件大小任意,所以不能將文件內(nèi)容全部讀入到內(nèi)存中進(jìn)行比較(更專業(yè)點(diǎn)說,需要使用非緩存的比較方式)


  3. 不依賴第三方庫


  4. 越快越好


為了選出最優(yōu)的解決方案,我搭建了一個簡單的命令行工程,準(zhǔn)備了兩個大小為912MB的文件,并且這兩個文件內(nèi)容完全相同.在本文的最后,你可以看到該工程的Main方法的代碼.


下面我們開始嘗試各個比較方法,選出最優(yōu)的解決方案:


比較兩個文件是否完全相同,首先想到的是用哈希算法(如MD5,SHA)算出兩個文件的哈希值,然后進(jìn)行比較.


寫一個MD5比較方法:


/// <summary>
/// MD5
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></param>
/// <returns></returns>
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)容,計(jì)算HASH值
           one = md5.ComputeHash(fs1);
       }
       using (var fs2 = File.Open(file2, FileMode.Open))
       {
           // 以FileStream讀取文件內(nèi)容,計(jì)算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秒,感覺還不錯.然而,這是最佳的解決方案嗎?


其實(shí)我們仔細(xì)想一下,答案應(yīng)該是否定的.


因?yàn)槿魏喂K惴ū举|(zhì)上都是對字節(jié)進(jìn)行一定的計(jì)算,而計(jì)算過程是要消耗時間的.


很多下載網(wǎng)站上提供了下載文件的哈希值,那是因?yàn)橄螺d的源文件本身不會改變,只需要計(jì)算一次源文件的哈希值,提供給用戶驗(yàn)證即可.


而我們的需求中,兩個文件都是不固定的,那么每次都要計(jì)算兩個文件的哈希值,就不太合適了.


所以,哈希比較這個方案被PASS.


這種求算法最優(yōu)解的問題,我以往的經(jīng)驗(yàn)是: 去stackoverflow查找 :)


經(jīng)過我的艱苦努力,找到了一個非常切題的答案: How to compare 2 files fast using .NET? https://stackoverflow.com/questions/1358510/how-to-compare-2-files-fast-using-net/1359947#1359947


得贊最多一個答案,將代碼改造了一下放入工程中:


該方法基本的原理是循環(huán)讀取兩個文件,每次讀取8個字節(jié),轉(zhuǎn)換為Int64,再進(jìn)行數(shù)值比較.那么效率如何呢?


Method: CompareByToInt64, Identical: True. Elapsed: 00:00:08.0918099

什么?8秒!竟然比MD5還慢?這不是SO得贊最多的答案嗎,怎么會這樣?


其實(shí)分析一下不難想到原因,因?yàn)槊看沃蛔x取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)行比較:


/// <summary>
/// 讀入到字節(jié)數(shù)組中比較(轉(zhuǎn)為String比較)
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></param>
/// <returns></returns>
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秒,比上一個方法強(qiáng)不了多少.


分析一下原因,在每次循環(huán)中,字符串的轉(zhuǎn)換是一個非常耗時的操作.那么有沒有不進(jìn)行類型轉(zhuǎn)換的字節(jié)數(shù)組比較方法呢?


我想到了LINQ中有一個比較序列的方法SequenceEqual,我們嘗試使用該方法比較:


/// <summary>
/// 讀入到字節(jié)數(shù)組中比較(使用LINQ的SequenceEqual比較)
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></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循環(huán)
       }
   }
   return true;
}


結(jié)果:


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


竟然比前兩個都要慢(實(shí)際這也是所有方案中最慢的一個),LINQ的SequenceEqual看來不是為了效率而生.


那么我們不用那些花哨的功能,回歸質(zhì)樸,老實(shí)兒的使用while循環(huán)比較字節(jié)數(shù)組怎么樣呢?


/// <summary>
/// 讀入到字節(jié)數(shù)組中比較(while循環(huán)比較字節(jié)數(shù)組)
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></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;
}


結(jié)果是....


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


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


試驗(yàn)到此,比較兩個900多MB的文件耗時1.5秒左右,讀者對于該方法是否滿意呢?


No!我不滿意!我相信通過努力,一定會找到更快的方法的!


同樣.NET CORE也在為了編寫高性能代碼而不斷的優(yōu)化中.


那么,我們?nèi)绾卫^續(xù)優(yōu)化我們的代碼呢?


我突然想到在C# 7.2中加入的一個新的值類型: Span<T>,它用來代表一段連續(xù)的內(nèi)存區(qū)域,并提供一系列可操作該區(qū)域的方法.


對于我們的需求,因?yàn)槲覀儾粫臄?shù)組的值,所以可以使用另外一個只讀的類型ReadOnlySpan<T>追求更高的效率.


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


/// <summary>
/// 讀入到字節(jié)數(shù)組中比較(ReadOnlySpan)
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></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);
           // 字節(jié)數(shù)組可直接轉(zhuǎn)換為ReadOnlySpan
           if (!((ReadOnlySpan<byte>)one).SequenceEqual((ReadOnlySpan<byte>)two)) return false;
           if (len1 == 0 || len2 == 0) break;  // 兩個文件都讀取到了末尾,退出while循環(huán)
       }
   }
   return true;
}


核心是用來比較的SequenceEqual方法,該方法是ReadOnlySpan的一個擴(kuò)展方法,要注意它只是方法名與LINQ中一樣,實(shí)現(xiàn)完全不同.


那么該方法的表現(xiàn)如何呢?


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


不 到 一 秒!


相對上一個已經(jīng)不錯的結(jié)果,速度提高了差不多40%!


對此結(jié)果,我個人覺得已經(jīng)很滿意了,如果各位有更快的方法,請不吝賜教,我非常歡迎!


關(guān)于Span<T>結(jié)構(gòu)類型,各位讀者如有興趣,可瀏覽該文章,該文有非常詳細(xì)的介紹.


后記


文中的代碼只是出于實(shí)驗(yàn)性質(zhì),實(shí)際應(yīng)用中仍可以繼續(xù)細(xì)節(jié)上的優(yōu)化, 如:


  • 如兩個文件大小不同,直接返回false


  • 如果兩個文件路徑相同,直接返回true


  • ...


試驗(yàn)工程的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);
   }
}


轉(zhuǎn)自:WAKU

cnblogs.com/waku/p/11069214.html


該文章在 2025/6/26 18:55:12 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
亚洲欧美日韩一区 | 亚洲视频免费观看 | 香蕉久久国产精品观看 | 亚洲国产中文在线二区三区 | 伊人久久综在合线亚洲第一页 | 日本中文免费观看视频 |