前言

最近项目有个需求,需要比较两个任意大小文件的内容是否相同,要求如下:

  • 项目是.NET CORE,所以使用C#进行编写比较方法
  • 文件大小任意,所以不能将文件内容全部读入到内存中进行比较(更专业点说,需要使用非缓存的比较方式)
  • 不依赖第三方库
  • 越快越好

为了选出最优的解决方案,我搭建了一个简单的命令行工程,准备了两个大小为912MB的文件,并且这两个文件内容完全相同.在本文的最后,你可以看到该工程的Main方法的代码.

下面我们开始尝试各个比较方法,选出最优的解决方案:

比较两个文件是否完全相同,首先想到的是用哈希算法(如MD5,SHA)算出两个文件的哈希值,然后进行比较.

废话少说,撸起袖子写一个MD5比较方法:

/// <summary>
/// MD5
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></param>
/// <returns></returns>
private static bool CompareByMD5(string file1, string file2)
{
 // 使用.NET内置的MD5库
 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);
 }
 // 将MD5结果(字节数组)转换成字符串进行比较
 return BitConverter.ToString(one) == BitConverter.ToString(two);
 }
}

比较结果:

Method: CompareByMD5, Identical: True. Elapsed: 00:00:05.7933178

耗时5.79秒,感觉还不错.然而,这是最佳的解决方案吗"external nofollow" target="_blank" href="https://stackoverflow.com/questions/1358510/how-to-compare-2-files-fast-using-net/1359947#1359947">How to compare 2 files fast using .NET"htmlcode">

/// <summary>
/// https://stackoverflow.com/a/1359947
/// </summary>
/// <param name="file1"></param>
/// <param name="file2"></param>
/// <returns></returns>
private static bool CompareByToInt64(string file1, string file2)
{
 const int BYTES_TO_READ = sizeof(Int64); // 每次读取8个字节
 int iterations = (int)Math.Ceiling((double)new FileInfo(file1).Length / BYTES_TO_READ); // 计算读取次数

 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++)
 {
  // 循环读取到字节数组中
  fs1.Read(one, 0, BYTES_TO_READ);
  fs2.Read(two, 0, BYTES_TO_READ);

  // 转换为Int64进行数值比较
  if (BitConverter.ToInt64(one, 0) != BitConverter.ToInt64(two, 0))
  return false;
 }
 }

 return true;
}

该方法基本的原理是循环读取两个文件,每次读取8个字节,转换为Int64,再进行数值比较.那么效率如何呢"htmlcode">

/// <summary>
/// 读入到字节数组中比较(转为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循环
 }
 }

 return true;
}

结果:

Method: CompareByString, Identical: True. Elapsed: 00:00:07.8088732

耗时也接近8秒,比上一个方法强不了多少.

分析一下原因,在每次循环中,字符串的转换是一个非常耗时的操作.那么有没有不进行类型转换的字节数组比较方法呢"htmlcode">

/// <summary>
/// 读入到字节数组中比较(使用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循环
 }
 }

 return true;
}

结果:

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

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

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

/// <summary>
/// 读入到字节数组中比较(while循环比较字节数组)
/// </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;
}

结果是....

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

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

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

/// <summary>
/// 读入到字节数组中比较(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);
  // 字节数组可直接转换为ReadOnlySpan
  if (!((ReadOnlySpan<byte>)one).SequenceEqual((ReadOnlySpan<byte>)two)) return false;
  if (len1 == 0 || len2 == 0) break; // 两个文件都读取到了末尾,退出while循环
 }
 }

 return true;
}

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

那么该方法的表现如何呢"color: #ff0000">后记

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

  • 如两个文件大小不同,直接返回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);
 }
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。

标签:
.net,core比较文件内容,.net,core教程

免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
狼山资源网 Copyright www.pvsay.com

《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线

暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。

艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。

《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。