2つのバイナリファイルを比較します。そのうちの1つは、私が最初に保存したときから、事前に計算されたCRC32を使用してサーバーに既に保存されています。
CRCが異なる場合、ファイルは明らかに異なります。ただし、CRCが同じ場合、ファイルが同じであることはわかりません。したがって、私は2つのストリームを比較する効率的な方法を探しています。1つは投稿されたファイルから、もう1つはファイルシステムからです。
私はストリームの専門家ではありませんが、メモリの使用に関する限り、ここで簡単に自分の足で撃つことができることをよく知っています。
static bool FileEquals(string fileName1, string fileName2)
{
// Check the file size and CRC equality here.. if they are equal...
using (var file1 = new FileStream(fileName1, FileMode.Open))
using (var file2 = new FileStream(fileName2, FileMode.Open))
return FileStreamEquals(file1, file2);
}
static bool FileStreamEquals(Stream stream1, Stream stream2)
{
const int bufferSize = 2048;
byte[] buffer1 = new byte[bufferSize]; //buffer size
byte[] buffer2 = new byte[bufferSize];
while (true) {
int count1 = stream1.Read(buffer1, 0, bufferSize);
int count2 = stream2.Read(buffer2, 0, bufferSize);
if (count1 != count2)
return false;
if (count1 == 0)
return true;
// You might replace the following with an efficient "memcmp"
if (!buffer1.Take(count1).SequenceEqual(buffer2.Take(count2)))
return false;
}
}
読み取りストリームチャンクのループでInt64比較を使用して、「memcmp」を高速化しました。これにより、時間は約1/4に短縮されました。
private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2)
{
const int bufferSize = 2048 * 2;
var buffer1 = new byte[bufferSize];
var buffer2 = new byte[bufferSize];
while (true)
{
int count1 = stream1.Read(buffer1, 0, bufferSize);
int count2 = stream2.Read(buffer2, 0, bufferSize);
if (count1 != count2)
{
return false;
}
if (count1 == 0)
{
return true;
}
int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64));
for (int i = 0; i < iterations; i++)
{
if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64)))
{
return false;
}
}
}
}
Crcに依存したくない場合は、次のようにします。
/// <summary>
/// Binary comparison of two files
/// </summary>
/// <param name="fileName1">the file to compare</param>
/// <param name="fileName2">the other file to compare</param>
/// <returns>a value indicateing weather the file are identical</returns>
public static bool CompareFiles(string fileName1, string fileName2)
{
FileInfo info1 = new FileInfo(fileName1);
FileInfo info2 = new FileInfo(fileName2);
bool same = info1.Length == info2.Length;
if (same)
{
using (FileStream fs1 = info1.OpenRead())
using (FileStream fs2 = info2.OpenRead())
using (BufferedStream bs1 = new BufferedStream(fs1))
using (BufferedStream bs2 = new BufferedStream(fs2))
{
for (long i = 0; i < info1.Length; i++)
{
if (bs1.ReadByte() != bs2.ReadByte())
{
same = false;
break;
}
}
}
}
return same;
}
そのcrcをsha1署名に変更すると、異なるが同じ署名を持つ可能性は天文学的に小さい
受け入れられた回答に指摘されたエラーがありましたが、修正されませんでした。ストリーム読み取り呼び出しは、要求されたすべてのバイトを返すことが保証されていません。
BinaryReaderReadBytes呼び出しは、ストリームの終わりに最初に到達しない限り、要求されたバイト数を返すことが保証されています。
次のコードは、比較を行うためにBinaryReaderを利用しています。
static private bool FileEquals(string file1, string file2)
{
using (FileStream s1 = new FileStream(file1, FileMode.Open, FileAccess.Read, FileShare.Read))
using (FileStream s2 = new FileStream(file2, FileMode.Open, FileAccess.Read, FileShare.Read))
using (BinaryReader b1 = new BinaryReader(s1))
using (BinaryReader b2 = new BinaryReader(s2))
{
while (true)
{
byte[] data1 = b1.ReadBytes(64 * 1024);
byte[] data2 = b2.ReadBytes(64 * 1024);
if (data1.Length != data2.Length)
return false;
if (data1.Length == 0)
return true;
if (!data1.SequenceEqual(data2))
return false;
}
}
}
CRCをチェックする前でも、2つのファイルの長さと日付をチェックして、CRCチェックを回避することができます。
しかし、ファイルの内容全体を比較する必要がある場合、私が見た1つの巧妙なトリックは、CPUのビット数に等しいストライドでバイトを読み取ることです。たとえば、32ビットPCでは、一度に4バイトを読み取り、それらをint32として比較します。 64ビットPCでは、一度に8バイトを読み取ることができます。これは、バイト単位で実行する場合と比べて、およそ4倍または8倍高速です。また、安全ではないコードブロックを使用して、ビットシフトを大量に実行してORを実行する代わりにポインタを使用して、バイトをネイティブのintサイズにすることもできます。
IntPtr.Sizeを使用して、現在のプロセッサアーキテクチャの理想的なサイズを決定できます。