2009-06-09 13 views
29

Voglio confrontare due file binari. Uno di questi è già memorizzato sul server con un CRC32 precalcolato nel database da quando l'ho memorizzato in origine.Confronta file binari in C#

So che se il CRC è diverso, i file sono decisamente diversi. Tuttavia, se il CRC è lo stesso, non so se i file lo sono. Quindi, sto cercando un modo efficiente per confrontare i due flussi: uno dal file pubblicato e uno dal file system.

Non sono un esperto di streaming, ma sono ben consapevole che potrei facilmente spararmi ai piedi qui per quanto riguarda l'utilizzo della memoria.

risposta

42
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; 
    } 
} 
+3

Richiedi conunt1 == count2 potrebbe essere inaccurato, poiché Stream.Read è libero di restituire un blocco con una lunghezza inferiore al conteggio byte richiesto. vedi http://msdn.microsoft.com/en-us/library/vstudio/system.io.stream.read(v=vs.100).aspx – Karata

+0

Grazie per la soluzione Mehrdad. Avete bisogno di ricevere chiamate? Ho provato solo 'if (! Buffer1.SequenceEqual (buffer2))' e sembra funzionare. –

+1

@Ozgur funziona ma è IMO meno efficiente e poco intuitivo. –

3

se si cambia che crc ad una firma SHA1 le probabilità di essere diversi, ma con la stessa firma sono astronomicly piccola

+0

si dovrebbe mai fare affidamento su che in applicazioni più gravi. È come controllare l'hash in una ricerca hashtable senza confrontare le chiavi effettive! –

+1

sfortunatamente si può garantire che l'unica volta che si incasina sarà assolutamente fondamentale, probabilmente quella grande. –

+0

@Simon - hehe molto vero. @Mehrdad - No probabilmente no ma ridurrebbe di molto i tempi che dovresti controllare per essere super-uber sicuri. – albertjan

2

È possibile controllare la lunghezza e le date dei due file anche prima di controllare il CRC per evitare possibilmente il controllo CRC.

Ma se si deve confrontare l'intero contenuto del file, un trucco pulito che ho visto è la lettura dei byte in passi uguale al testimone della CPU. Ad esempio, su un PC a 32 bit, leggere 4 byte alla volta e confrontarli come int32. Su un PC a 64 bit è possibile leggere 8 byte alla volta. Questo è circa 4 o 8 volte più veloce di farlo byte per byte. Probabilmente vorresti anche usare un blocco di codice non sicuro in modo che tu possa usare i puntatori invece di fare un po 'di spostamento dei bit e OR per ottenere i byte nelle dimensioni int native.

È possibile utilizzare IntPtr.Size per determinare la dimensione ideale per l'architettura del processore corrente.

+2

Puoi fornire un esempio di codice su come lo faresti? – Svish

20

Ho velocizzato il "memcmp" utilizzando un confronto Int64 in un ciclo sui blocchi del flusso di lettura. Questo tempo ridotto a circa 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; 
       } 
      } 
     } 
    } 
+0

Questo è vantaggioso solo per le CPU a 64 bit o sarà utile anche per le CPU a 32 bit? – Pretzel

+0

Non dovrebbe essere importante se è in esecuzione un sistema operativo a 32 o 64 bit. Ma non l'ho mai provato su una pura CPU a 32 bit. Devi provarlo e forse basta cambiare Int64 in int32. Ma la maggior parte delle CPU più o meno moderne sono in grado di eseguire operazioni a 64 bit (x86 dal 2004)? Vai avanti e provalo! – Lars

+0

Vedere i commenti su [questa risposta] (http://stackoverflow.com/a/968980/157247). Affidarsi a 'count1' che equivale a' count2' non è affidabile. –

6

Questo è come io lo farei se non si vuole fare affidamento su 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; 
    } 
+1

info2 dovrebbe assumere fileName2 come argomento anziché fileName1. Altrimenti, bella soluzione :-). – fbastian

0

La risposta accettata ha avuto un errore che è stato sottolineato, ma mai corretto: flusso di leggere le chiamate non sono garantiti per restituire tutti i byte richiesti.

BinaryReaderreadBytes chiamate sono garantiti per tornare tanti byte come richiesto a meno che il fine del flusso viene raggiunto prima.

Il seguente codice sfrutta BinaryReader per fare il confronto:

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; 
      } 
     } 
    }