最速は、二つのファイルNET COREコンテンツを比較します
最近のプロジェクトは、需要が、コンテンツは任意のサイズの2つのファイルは、以下の同じ比較する必要があります。
- プロジェクトは、.NET COREので、C#での比較方法を書きます
- 任意のファイルサイズは、(比較的非キャッシュモードを使用する必要があり、より専門的なポイント)を比較することにより、メモリへのファイルの内容全体を読み込むことができませんので、
- サードパーティ製のライブラリに依存しないでください
- 早いほど良いです
最適なソリューションを選択するために、私は、単純なコマンドラインプロジェクトを構築して912メガバイトの2つのファイルのサイズを用意し、2つのファイルの内容は同じです。この記事の最後に、あなたがプロジェクトを見ることができますコードメイン方法。
ここでは、様々な方法を比較しようとして開始し、最適なソリューションを選択します:
2つのファイルが同一である比較し、最初に考えた(例えば、MD5、SHAのような)ハッシュアルゴリズムを使用して2つのファイルのハッシュ値を計算することである、そして次に比較します。
さらに騒ぎがなければ、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秒かかり、それはかなり良い感じ。しかし、これが最善の解決策とは?
実際には、我々は慎重に考える、答えはノーです。
本質的にバイト特定の計算任意のハッシュアルゴリズム、および計算のすべては、時間がかかることがあるからです。
ダウンロードしたソースファイル自体は変更されませんので、多くのダウンロードサイトでダウンロードしたファイルのハッシュ値を提供し、あなただけはすることができ、ユーザ認証に提供するプライマリソース文書のハッシュ値を計算する必要があります。
そして、我々の需要、2つのファイルが固定されていない場合、ハッシュ値は、二つの文書のそれぞれについて計算され、それは適切ではありません。
そのため、ハッシュは、このプログラムがPASSで比較します。
これを解決する最適なソリューションアルゴリズム、私の過去の経験がある: StackOverflowのを探しに行く :)
私のハードワークの後、非常に適切な答えを見つけました: ファイルの比較FAST 2にどのように.NETを使用していますか?
ほとんどが答えを得るように、コードがプロジェクトに変換します。
/// <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に変換し、二つの文書を読むためのサイクルであり、その後、数値比較。それでは、どのように効率的ですか?
Method: CompareByToInt64, Identical: True. Elapsed: 00:00:08.0918099
何?MD5よりもさらに遅い8秒!?それは、ほとんどのSOどのようにこれは可能でしょうか?、それに答える気に入っていませんでしたか
実際には、分析は、各時間はわずか8つのバイトを読んでいるので、プログラムが頻繁にパフォーマンスの低下が生じ、IO操作を行った。これは、SOや迷信ああ上の答えと思われる、その理由を考えることは困難ではありません!
そして、最適化の方向がIO操作による損失を軽減する方法になります。
少なすぎる各8バイトのため、我々は、アレイに読み出し当たり1024バイト。例えば1024のバイトなどのバイトより大きなアレイを定義し、バイト配列を比較します。
しかし、これは、新たな問題がすぐに2つのバイト配列が同じである比較する方法であるもたらしますか?
私の最初の思想は、比較文字列にバイト配列を変換---- MD5方法に使用されます。
/// <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秒にどのくらいのより少し方法を取りました。
各サイクルでは、文字列の変換は非常に時間のかかる操作で、その理由を分析します。バイト配列の比較方法の型変換ではないことはありますか?
私は、LINQの配列比較方法があると思いSequenceEqual
、私たちはこの方法の比較を使用しようとは:
/// <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
最初の二つより遅いことが判明した(実際には、これは全てのプログラムの最も遅いa)は、SequenceEqualのLINQ効率が生まれていないようです。
だから我々は、whileループを使用するには、どのように正直な子がバイト配列を比較し、それらの派手な機能を持って簡潔に戻らないのですか?
/// <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秒!画期的な!時々不器用な方法が、良さそうですね!
これをテストし、2つの900メガバイトのファイルを比較することを行う方法を読者が満足するかどうかを、約1.5秒かかりますか?
いいえ!私は満足していない!私は、私たちの努力を通じて、我々はより高速な方法を見つけるだろうと信じています!
また、良好なパフォーマンスと継続的な最適化のためのコードを書くためにも、.NET CORE。
それでは、どのよう我々はコードを最適化していきますか?
:私は突然新しい値型がC#7.2に加え考え Span<T>
、それはメモリの連続領域を表すために使用される、および方法の一連の動作可能領域を提供します。
我々は、配列の値を変更しませんので、我々のニーズのために、あなたは読み取り専用の別のタイプを使用することができますReadOnlySpan<T>
効率化の追求を。
コードを変更し、使用しますReadOnlySpan<T>
:
/// <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でそれに注意を払う、拡張メソッド
だから、どのようにメソッドのパフォーマンス?
Method: CompareByReadOnlySpan, Identical: True. Elapsed: 00:00:00.9287703
秒未満!
、比較的良好な結果で、ほぼ40%高速化されました!
この結果は、私は個人的にあなたがより高速な方法を持っている場合は、私たちに知らせてください、非常に満足を感じ、私は非常に歓迎!
Span<T>
興味がある読者構造の種類、あなたが閲覧することができます記事を、記事は非常に詳細な紹介があります。
追伸
-
唯一の実験的、実用的なアプリケーションのためのコードのテキスト内容を最適化し続けることができる、例えば:
- 大きさの異なる2つのファイルは、直接的にはfalseを返します
- 二つの同一のファイルパス場合は、直接、trueを返します
- ...
-
パイロットプロジェクトのソースの主な方法:
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); } }
仕上がり。