2009-06-09 32 views
29

Quiero comparar dos archivos binarios. Uno de ellos ya está almacenado en el servidor con un CRC32 precalculado en la base de datos desde que lo almacené originalmente.Comparar archivos binarios en C#

Sé que si el CRC es diferente, entonces los archivos son definitivamente diferentes. Sin embargo, si el CRC es el mismo, no sé si los archivos sí lo están. Por lo tanto, estoy buscando una manera agradable y eficiente de comparar las dos corrientes: una del archivo publicado y otra del sistema de archivos.

No soy un experto en transmisiones, pero soy consciente de que podría dispararme fácilmente en el pie aquí en lo que respecta al uso de la memoria.

Respuesta

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

Requiere conunt1 == count2 podría ser impreciso, ya que Stream.Read puede devolver un bloque que tenga una longitud inferior al conteo de bytes solicitado. ver http://msdn.microsoft.com/en-us/library/vstudio/system.io.stream.read(v=vs.100).aspx – Karata

+0

Gracias por la solución Mehrdad. ¿Necesita las llamadas a Take? Intenté solo 'if (! Buffer1.SequenceEqual (buffer2))' y parece que funciona. –

+1

@Ozgur funciona, pero es menos eficiente y no tiene mucho principio IMO. –

3

si cambia que el CCR a una firma SHA1 las posibilidades de que sea diferente pero con la misma firma son astronomicly pequeña

+0

Nunca se debe confiar en que, en aplicaciones más graves. ¡Es como comprobar el hash en una búsqueda de hashtable sin comparar las claves reales! –

+1

lamentablemente puede garantizar que la única vez que se arruine será absolutamente crítico, probablemente ese gran lanzamiento. –

+0

@Simon - jeje muy cierto. @Mehrdad - Probablemente no, pero reduciría enormemente los tiempos que tendría que comprobar para estar súper seguro. – albertjan

2

Puede comprobar la duración y las fechas de los dos archivos, incluso antes de comprobar el CRC, para evitar posiblemente la verificación CRC.

Pero si tiene que comparar todo el contenido del archivo, un buen truco que he visto es leer los bytes en pasos iguales al bitness de la CPU. Por ejemplo, en una PC de 32 bits, lea 4 bytes a la vez y compárelos como int32. En una PC de 64 bits puedes leer 8 bytes a la vez. Esto es aproximadamente 4 u 8 veces más rápido que hacerlo byte a byte. También es probable que desee utilizar un bloque de código inseguro para que pueda utilizar punteros en lugar de hacer un montón de cambios de bit y OR para obtener los bytes en los tamaños int nativos.

Puede usar IntPtr.Size para determinar el tamaño ideal para la arquitectura del procesador actual.

+2

¿Podría proporcionarnos un ejemplo de código sobre cómo lo haría? – Svish

20

Aceleré el "memcmp" utilizando una comparación Int64 en un bucle sobre los fragmentos de flujo de lectura. Esto redujo el tiempo a aproximadamente 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

¿Es esto ventajoso solo para CPU de 64 bits o esto también ayudará en CPU de 32 bits? – Pretzel

+0

No debería importar si tiene un sistema operativo de 32 o 64 bits en ejecución. Pero nunca lo intenté en una CPU pura de 32 bits. Tienes que probarlo y tal vez simplemente cambiar Int64 a int32. Pero, ¿no son la mayoría de las CPU más o menos modernas capaces de operaciones de 64 bits (x86 desde 2004)? ¡Sigue adelante e inténtalo! – Lars

+0

Ver comentarios sobre [esta respuesta] (http://stackoverflow.com/a/968980/157247). Confiar en 'count1' igualando' count2' no es confiable. –

6

Esta es la forma en que lo haría si no quiere depender de 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 debería tomar fileName2 como argumento en lugar de fileName1. De lo contrario, buena solución :-). – fbastian

0

La respuesta aceptada tenía un error que se ha señalado, pero nunca corrigió: flujo de leer las llamadas no se garantiza que devolverán todos los bytes solicitados.

BinaryReaderreadBytes llamadas están garantizados para volver tantos bytes como se pide a menos que el final de la secuencia que se alcance primero.

El siguiente código se aprovecha de BinaryReader hacer la comparación:

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