Los enfoques típicos recomiendan leer el binario a través de FileStream y compararlo byte por byte.
- ¿Sería más rápida una comparación de suma de comprobación como CRC?
- ¿Hay alguna biblioteca .NET que pueda generar una suma de verificación para un archivo?
Respuestas:
Una comparación de suma de verificación probablemente será más lenta que una comparación byte por byte.
Para generar una suma de verificación, deberá cargar cada byte del archivo y realizar el procesamiento en él. Luego tendrá que hacer esto en el segundo archivo. El procesamiento casi definitivamente será más lento que la verificación de comparación.
En cuanto a generar una suma de verificación: puede hacerlo fácilmente con las clases de criptografía. Aquí hay un breve ejemplo de cómo generar una suma de verificación MD5 con C #.
Sin embargo, una suma de verificación puede ser más rápida y tener más sentido si puede precalcular la suma de verificación del caso "prueba" o "base". Si tiene un archivo existente y está verificando si un nuevo archivo es el mismo que el existente, calcular previamente la suma de verificación en su archivo "existente" significaría que solo necesita hacer DiskIO una vez, en el archivo nuevo. Esto probablemente sería más rápido que una comparación byte por byte.
fuente
El método más lento posible es comparar dos archivos byte por byte. Lo más rápido que he podido llegar es una comparación similar, pero en lugar de un byte a la vez, usaría una matriz de bytes de tamaño Int64 y luego compararía los números resultantes.
Esto es lo que se me ocurrió:
En mis pruebas, pude ver que esto superaba un escenario ReadByte () directo en casi 3: 1. Con un promedio de más de 1000 carreras, obtuve este método a 1063 ms, y el método a continuación (comparación directa byte por byte) a 3031 ms. El hashing siempre regresó en menos de un segundo alrededor de un promedio de 865 ms. Esta prueba fue con un archivo de video de ~ 100MB.
Aquí están los métodos ReadByte y hash que utilicé, para fines de comparación:
fuente
FilesAreEqual_Hash
método también debería tener unusing
flujo en ambas secuencias de archivosReadByte
, de lo contrario se mantendrá en ambos archivos.FileStream.Read()
realidad puede leer menos bytes que el número solicitado. Deberías usarStreamReader.ReadBlock()
en su lugar.Si no decide que realmente necesita una comparación completa byte por byte (consulte otras respuestas para analizar el hash), entonces la solución más fácil es:
• por
System.IO.FileInfo
instancias:• para
System.String
nombres de ruta:A diferencia de otras respuestas publicadas, esto es concluyentemente correcto para cualquier tipo de archivo: binario, texto, medios, ejecutable, etc., pero como una comparación binaria completa , archivos que difieren solo en formas "sin importancia" (como BOM , línea -final , codificación de caracteres , metadatos de medios, espacios en blanco, relleno, comentarios de código fuente, etc.) siempre se considerarán no iguales .
Este código carga ambos archivos en la memoria por completo, por lo que no debe usarse para comparar archivos verdaderamente gigantescos . Más allá de esa advertencia importante, la carga completa no es realmente una penalización dado el diseño de .NET GC (porque está optimizado fundamentalmente para mantener las asignaciones pequeñas y de corta duración extremadamente baratas ), y de hecho incluso podría ser óptimo cuando se espera un tamaño de archivo a ser inferior a 85K , porque el uso de un mínimo de código de usuario (como se muestra aquí) implica delegar al máximo los problemas de rendimiento archivo al
CLR
,BCL
yJIT
que se benefician de (por ejemplo) la última tecnología de diseño, código de sistema, y optimizaciones de tiempo de ejecución de adaptación.Además, para tales escenarios de días de trabajo, las preocupaciones sobre el rendimiento de la comparación byte a byte a través de
LINQ
enumeradores (como se muestra aquí) son discutibles, ya que golpear el disco a̲t̲ a̲l̲l̲ para E / S de archivo empequeñecerá, en varios órdenes de magnitud, los beneficios de las diversas alternativas de comparación de memoria. Por ejemplo, a pesar de queSequenceEqual
no , de hecho, nos dará la "optimización" de abandonar en la primera falta de coincidencia , esto importa poco después de tener ya leídos contenidos de los ficheros, cada uno totalmente necesaria para confirmar el partido ..fuente
Además de la respuesta de Reed Copsey :
El peor de los casos es donde los dos archivos son idénticos. En este caso, es mejor comparar los archivos byte por byte.
Si los dos archivos no son idénticos, puede acelerar un poco las cosas detectando antes que no son idénticos.
Por ejemplo, si los dos archivos tienen una longitud diferente, entonces sabe que no pueden ser idénticos y ni siquiera tiene que comparar su contenido real.
fuente
Se está volviendo aún más rápido si no lee en fragmentos pequeños de 8 bytes pero coloca un bucle, leyendo un fragmento más grande. Reduje el tiempo de comparación promedio a 1/4.
fuente
count1 != count2
no es correcto.Stream.Read()
puede devolver menos del recuento que ha proporcionado, por varias razones.Int64
bloques, es posible que desee para calcular el tamaño de la siguiente manera:const int bufferSize = 1024 * sizeof(Int64)
.Lo único que puede hacer que una comparación de suma de comprobación sea un poco más rápida que una comparación byte por byte es el hecho de que está leyendo un archivo a la vez, lo que reduce el tiempo de búsqueda del cabezal del disco. Sin embargo, esa ligera ganancia puede muy bien ser absorbida por el tiempo adicional de calcular el hash.
Además, una comparación de suma de comprobación, por supuesto, solo tiene alguna posibilidad de ser más rápida si los archivos son idénticos. Si no lo son, una comparación byte por byte terminaría en la primera diferencia, lo que lo hace mucho más rápido.
También debe considerar que una comparación de código hash solo le dice que es muy probable que los archivos sean idénticos. Para estar 100% seguro, necesita hacer una comparación byte por byte.
Si el código hash, por ejemplo, es de 32 bits, está aproximadamente 99.99999998% seguro de que los archivos son idénticos si los códigos hash coinciden. Eso está cerca del 100%, pero si realmente necesita 100% de certeza, no es eso.
fuente
1 - (1 / (2^32))
, que es la probabilidad de que cualquier archivo tenga algún hash de 32 bits. La probabilidad de que dos archivos diferentes tengan el mismo hash es la misma, porque el primer archivo proporciona el valor de hash "dado", y solo necesitamos considerar si el otro archivo coincide o no con ese valor. Las posibilidades con el hashing de 64 y 128 bits disminuyen a 99.999999999999999994% y 99.9999999999999999999999999999999999997% (respectivamente), como si eso fuera importante con esos números insondables.Editar: ¡ Este método no funcionaría para comparar archivos binarios!
En .NET 4.0, la
File
clase tiene los siguientes dos métodos nuevos:Lo que significa que podrías usar:
fuente
Honestamente, creo que necesitas podar tu árbol de búsqueda tanto como sea posible.
Cosas para verificar antes de ir byte a byte:
Además, leer bloques grandes a la vez será más eficiente ya que las unidades leen bytes secuenciales más rápidamente. Ir byte a byte causa no solo muchas más llamadas al sistema, sino que hace que el cabezal de lectura de un disco duro tradicional busque más adelante y atrás si ambos archivos están en la misma unidad.
Lea el fragmento A y el fragmento B en un búfer de bytes, y compárelos (NO use Array.Equals, vea los comentarios). Ajuste el tamaño de los bloques hasta que encuentre lo que cree que es un buen intercambio entre memoria y rendimiento. También puede hacer varios subprocesos en la comparación, pero no haga varios subprocesos en las lecturas del disco.
fuente
Mi respuesta es un derivado de @lars pero corrige el error en la llamada a
Stream.Read
. También agrego algunas comprobaciones de ruta rápida que tenían otras respuestas y validación de entrada. En resumen, esta debería ser la respuesta:O si quieres ser súper genial, puedes usar la variante asíncrona:
fuente
Mis experimentos muestran que definitivamente ayuda llamar Stream.ReadByte () menos veces, pero usar BitConverter para empaquetar bytes no hace mucha diferencia en comparación de bytes en una matriz de bytes.
Por lo tanto, es posible reemplazar ese bucle "Math.Ceiling and iterations" en el comentario anterior con el más simple:
Supongo que tiene que ver con el hecho de que BitConverter.ToInt64 necesita hacer un poco de trabajo (verifique los argumentos y luego realice el cambio de bits) antes de comparar y eso termina siendo la misma cantidad de trabajo que comparar 8 bytes en dos matrices .
fuente
Si los archivos no son demasiado grandes, puede usar:
Solo será factible comparar hashes si los hashes son útiles para almacenar.
(Editó el código a algo mucho más limpio).
fuente
Otra mejora en archivos grandes con una longitud idéntica podría ser no leer los archivos secuencialmente, sino comparar bloques más o menos aleatorios.
Puede usar múltiples hilos, comenzando en diferentes posiciones en el archivo y comparando hacia adelante o hacia atrás.
De esta forma, puede detectar cambios en el medio / final del archivo, más rápido de lo que lo haría utilizando un enfoque secuencial.
fuente
Si solo necesita comparar dos archivos, supongo que la forma más rápida sería (en C, no sé si es aplicable a .NET)
OTOH, si necesita encontrar si hay archivos duplicados en un conjunto de N archivos, entonces la forma más rápida es, sin duda, usar un hash para evitar comparaciones N-way bit a bit.
fuente
Algo (con suerte) razonablemente eficiente:
fuente
Estas son algunas funciones de utilidad que le permiten determinar si dos archivos (o dos secuencias) contienen datos idénticos.
He proporcionado una versión "rápida" que es multiproceso, ya que compara los conjuntos de bytes (cada búfer lleno de lo que se ha leído en cada archivo) en diferentes subprocesos utilizando Tareas.
Como se esperaba, es mucho más rápido (alrededor de 3 veces más rápido) pero consume más CPU (porque es multiproceso) y más memoria (porque necesita dos buffers de matriz de bytes por hilo de comparación).
fuente
Creo que hay aplicaciones donde "hash" es más rápido que comparar byte por byte. Si necesita comparar un archivo con otros o tener una miniatura de una foto que pueda cambiar. Depende de dónde y cómo se use.
Aquí, puedes obtener lo que es más rápido.
Opcionalmente, podemos guardar el hash en una base de datos.
Espero que esto pueda ayudar
fuente
Otra respuesta, derivada de @chsh. MD5 con usos y accesos directos para el mismo archivo, el archivo no existe y diferentes longitudes:
fuente
if (i>=secondHash.Length ...
¿Bajo qué circunstancias serían dos hashes MD5 de diferentes longitudes?Esto he encontrado que funciona bien al comparar primero la longitud sin leer los datos y luego comparar la secuencia de bytes de lectura
fuente