Uma comparação de soma de verificação provavelmente será mais lenta que uma comparação de byte a byte.
Para gerar uma soma de verificação, você precisará carregar cada byte do arquivo e executar o processamento nele. Você precisará fazer isso no segundo arquivo. Definitivamente, o processamento será mais lento que a verificação de comparação.
No entanto, uma soma de verificação pode ser mais rápida e fazer mais sentido se você puder pré-calcular a soma de verificação do caso "teste" ou "base". Se você possui um arquivo existente e está verificando se um novo arquivo é igual ao existente, pré-calcular a soma de verificação no arquivo "existente" significaria apenas a necessidade de fazer o DiskIO uma vez, no diretório novo arquivo. Isso provavelmente seria mais rápido que uma comparação de byte a byte.
Certifique-se de levar em consideração onde seus arquivos estão localizados. Se você estiver comparando arquivos locais a um backup do outro lado do mundo (ou em uma rede com largura de banda horrível), é melhor fazer o hash primeiro e enviar uma soma de verificação pela rede em vez de enviar um fluxo de bytes para comparar.
Kim
@ReedCopsey: Estou tendo um problema semelhante, pois preciso armazenar arquivos de entrada / saída produzidos por várias elaborações que devem conter muitas duplicações. Eu pensei em usar o hash pré-computado, mas você acha que posso razoavelmente supor que se o hash 2 (por exemplo, MD5) for igual, os 2 arquivos serão iguais e evitaremos uma comparação mais byte-2-byte? Até onde eu sei, colisões MD5 / SHA1 etc são realmente improváveis ...
digEmAll
1
@digEmAll A chance de colisão é baixa - no entanto, você sempre pode fazer um hash mais forte - por exemplo: use SHA256 em vez de SHA1, o que reduzirá ainda mais a probabilidade de colisões.
Reed Copsey
obrigado pela sua resposta - estou entrando no .net. Estou assumindo que, se alguém estiver usando a técnica de hashcode / check sum, os hashes da pasta principal serão armazenados persistentemente em algum lugar? por curiosidade, como você o armazenaria para um aplicativo WPF - o que você faria? (Atualmente, estou olhando xml, arquivos de texto ou bancos de dados).
BKSpurgeon
139
O método mais lento possível é comparar dois arquivos byte a byte. O mais rápido que consegui fazer é uma comparação semelhante, mas, em vez de um byte por vez, você usaria uma matriz de bytes dimensionada para Int64 e, em seguida, compararia os números resultantes.
Aqui está o que eu vim com:
constint BYTES_TO_READ =sizeof(Int64);staticboolFilesAreEqual(FileInfo first,FileInfo second){if(first.Length!= second.Length)returnfalse;if(string.Equals(first.FullName, second.FullName,StringComparison.OrdinalIgnoreCase))returntrue;int iterations =(int)Math.Ceiling((double)first.Length/ BYTES_TO_READ);
using (FileStream fs1 = first.OpenRead())
using (FileStream fs2 = second.OpenRead()){byte[] one =newbyte[BYTES_TO_READ];byte[] two =newbyte[BYTES_TO_READ];for(int i =0; i < iterations; i++){
fs1.Read(one,0, BYTES_TO_READ);
fs2.Read(two,0, BYTES_TO_READ);if(BitConverter.ToInt64(one,0)!=BitConverter.ToInt64(two,0))returnfalse;}}returntrue;}
Nos meus testes, eu pude ver isso superar um cenário simples de ReadByte () em quase 3: 1. Em média, mais de 1000 execuções, obtive esse método em 1063ms e o método abaixo (comparação direta de bytes por byte) em 3031ms. O hash sempre voltava em um segundo em torno de uma média de 865ms. Este teste foi realizado com um arquivo de vídeo de ~ 100 MB.
Aqui estão os métodos ReadByte e hash que eu usei, para fins de comparação:
staticboolFilesAreEqual_OneByte(FileInfo first,FileInfo second){if(first.Length!= second.Length)returnfalse;if(string.Equals(first.FullName, second.FullName,StringComparison.OrdinalIgnoreCase))returntrue;
using (FileStream fs1 = first.OpenRead())
using (FileStream fs2 = second.OpenRead()){for(int i =0; i < first.Length; i++){if(fs1.ReadByte()!= fs2.ReadByte())returnfalse;}}returntrue;}staticboolFilesAreEqual_Hash(FileInfo first,FileInfo second){byte[] firstHash = MD5.Create().ComputeHash(first.OpenRead());byte[] secondHash = MD5.Create().ComputeHash(second.OpenRead());for(int i=0; i<firstHash.Length; i++){if(firstHash[i]!= secondHash[i])returnfalse;}returntrue;}
O FilesAreEqual_Hashmétodo deve ter um usingnos dois fluxos de arquivos também como o ReadBytemétodo, caso contrário, ele permanecerá nos dois arquivos.
Ian Mercer
2
Observe que, FileStream.Read()na verdade, pode ler menos bytes que o número solicitado. Você deve usar em seu StreamReader.ReadBlock()lugar.
Palec
2
Na versão Int64, quando o comprimento do fluxo não é múltiplo do Int64, a última iteração está comparando os bytes não preenchidos usando o preenchimento da iteração anterior (que também deve ser igual, para que fique bem). Além disso, se o comprimento do fluxo for menor que sizeof (Int64), os bytes não preenchidos serão 0, pois o C # inicializa matrizes. IMO, o código provavelmente deve comentar essas esquisitices.
crokusek
46
Se você não decidir você realmente precisa de uma comparação completa byte a byte (veja outras respostas para a discussão de hashing), então a solução mais fácil é:
Diferentemente de outras respostas publicadas, isso é conclusivamente correto para qualquer tipo de arquivo: binário, texto, mídia, executável etc., mas como uma comparação binária completa , arquivos que diferem apenas de maneiras "sem importância" (como BOM , linha -ending , codificação de caracteres , metadados de mídia, espaço em branco, estofamento, comentários de código-fonte, etc.) será sempre considerado não-igual .
Esse código carrega os dois arquivos inteiramente na memória, portanto, não deve ser usado para comparar arquivos verdadeiramente gigantescos . Além dessa ressalva importante, o carregamento completo não é realmente uma penalidade, devido ao design do .NET GC (porque é fundamentalmente otimizado para manter alocações pequenas e de curta duração extremamente baratas ) e, de fato, pode ser ideal quando os tamanhos de arquivo são esperados a ser menos do que 85K , porque usar um mínimo de código de utilizador (como mostrado aqui) implica maximamente delegar problemas de desempenho arquivo para o CLR, BCLe JITpara benefício de (por exemplo) o mais recente projeto, código do sistema, e otimizações de tempo de execução adaptativos.
Além disso, para esses cenários de trabalho, preocupações com o desempenho da comparação de byte a byte através de LINQenumeradores (como mostrado aqui) são discutíveis, já que bater no disco a̲t̲ a̲l̲l̲ para E / S de arquivo diminui, por várias ordens de magnitude, os benefícios das várias alternativas de comparação de memória. Por exemplo, embora SequenceEqualrealmente nos dê a "otimização" de abandonar na primeira incompatibilidade , isso dificilmente importa depois de já ter buscado o conteúdo dos arquivos, cada um totalmente necessário para confirmar a correspondência.
este não parece bom para arquivos grandes. não é bom para o uso da memória, pois ele lerá os dois arquivos até o final antes de começar a comparar a matriz de bytes. É por isso que eu prefiro usar um leitor de stream com um buffer.
Krypto_47
3
@ Krypto_47 Eu discuti esses fatores e o uso apropriado no texto da minha resposta.
Glenn Slayden
33
Além da resposta de Reed Copsey :
O pior caso é onde os dois arquivos são idênticos. Nesse caso, é melhor comparar os arquivos byte a byte.
Se os dois arquivos não forem idênticos, você poderá acelerar um pouco as coisas, detectando antes que eles não são idênticos.
Por exemplo, se os dois arquivos tiverem tamanho diferente, você saberá que eles não podem ser idênticos e nem precisará comparar o conteúdo real.
Para ser concluído: o outro grande ganho será interrompido assim que os bytes na posição 1 forem diferentes.
Henk Holterman
6
@Henk: Eu achava que isso era muito óbvio :-)
dtb
1
Bom ponto de adicionar isso. Era óbvio para mim, então não o incluí, mas é bom mencionar.
Reed Copsey
16
Está ficando ainda mais rápido se você não ler em pequenos pedaços de 8 bytes, mas fizer um loop, lendo um pedaço maior. Reduzi o tempo médio de comparação para 1/4.
publicstaticboolFilesContentsAreEqual(FileInfo fileInfo1,FileInfo fileInfo2){bool result;if(fileInfo1.Length!= fileInfo2.Length){
result =false;}else{
using (var file1 = fileInfo1.OpenRead()){
using (var file2 = fileInfo2.OpenRead()){
result =StreamsContentsAreEqual(file1, file2);}}}return result;}privatestaticboolStreamsContentsAreEqual(Stream stream1,Stream stream2){constint bufferSize =1024*sizeof(Int64);var buffer1 =newbyte[bufferSize];var buffer2 =newbyte[bufferSize];while(true){int count1 = stream1.Read(buffer1,0, bufferSize);int count2 = stream2.Read(buffer2,0, bufferSize);if(count1 != count2){returnfalse;}if(count1 ==0){returntrue;}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))){returnfalse;}}}}}
Em geral, a verificação count1 != count2não está correta. Stream.Read()pode retornar menos do que a contagem que você forneceu, por vários motivos.
PORGES
1
Para garantir que o buffer irá realizar um número par de Int64blocos, você pode querer calcular o tamanho da seguinte forma: const int bufferSize = 1024 * sizeof(Int64).
Jack A.
14
A única coisa que pode fazer uma comparação de soma de verificação um pouco mais rápida que uma comparação de byte a byte é o fato de você estar lendo um arquivo de cada vez, reduzindo um pouco o tempo de busca da cabeça do disco. Esse pequeno ganho pode, no entanto, muito bem ser consumido pelo tempo adicional de cálculo do hash.
Além disso, é claro que uma comparação de soma de verificação só tem chance de ser mais rápida se os arquivos forem idênticos. Caso contrário, uma comparação de byte a byte terminaria na primeira diferença, tornando-a muito mais rápida.
Você também deve considerar que uma comparação de código de hash apenas informa que é muito provável que os arquivos sejam idênticos. Para ter 100% de certeza, você precisa fazer uma comparação byte a byte.
Se o código de hash, por exemplo, for de 32 bits, você terá 99,99999998% de certeza de que os arquivos serão idênticos se os códigos de hash corresponderem. Isso é quase 100%, mas se você realmente precisa de 100% de certeza, não é isso.
Use um hash maior e você poderá obter as chances de um falso positivo muito abaixo das chances que o computador errou ao fazer o teste.
Loren Pechtel
Não concordo com o tempo de hash vs o tempo de busca. Você pode fazer muitos cálculos durante uma única busca principal. Se as chances de os arquivos coincidirem, eu usaria um hash com muitos bits. Se houver uma chance razoável de uma partida, eu os compararia um bloco de cada vez, por exemplo, blocos de 1 MB. (Escolha um tamanho de bloco que 4k divida igualmente para garantir que você nunca divida setores.)
Loren Pechtel
1
Para explicar a figura de 99,999999998% de @ Guffa, é proveniente da computação 1 - (1 / (2^32)), que é a probabilidade de que um único arquivo tenha um hash de 32 bits. A probabilidade de dois arquivos diferentes terem o mesmo hash é a mesma, porque o primeiro arquivo fornece o valor de hash "fornecido" e precisamos considerar apenas se o outro arquivo corresponde ou não a esse valor. As chances de hash de 64 e 128 bits diminuem para 99,999999999999999994% e 99,99999999999999999999999999999999997% (respectivamente), como se isso importasse com esses números insondáveis.
Glenn Slayden 19/03/19
... De fato, o fato de que esses números são mais difíceis de entender para a maioria das pessoas do que a noção supostamente simples, embora verdadeira, de "infinitamente muitos arquivos colidindo no mesmo código de hash" pode explicar por que os humanos são irracionalmente suspeitos de aceitar hash-as- igualdade.
Glenn Slayden
13
Edit: Este método não funcionaria para comparar arquivos binários!
No .NET 4.0, a Fileclasse possui os dois novos métodos a seguir:
@ dtb: Não funciona para arquivos binários. Você provavelmente já estava digitando o comentário quando percebi isso e adicionei a edição na parte superior da minha postagem. : o
Sam Harwell
@ 280Z28: Eu não disse nada ;-)
dtb
Você também não precisaria armazenar os dois arquivos na memória?
RandomInsano 27/01
Observe que File também possui a função ReadAllBytes, que também pode usar SequenceEquals; portanto, use-a, pois funcionaria em todos os arquivos. E como o @RandomInsano disse, isso é armazenado na memória, portanto, embora seja perfeitamente bom usá-lo para arquivos pequenos, eu seria cuidadoso ao usá-lo com arquivos grandes.
DaedalusAlpha
1
@DaedalusAlpha Retorna um enumerável, para que as linhas sejam carregadas sob demanda e não armazenadas na memória o tempo todo. ReadAllBytes, por outro lado, retorna o arquivo inteiro como uma matriz.
IllidanS4 quer Monica de volta 17/03/19
7
Honestamente, acho que você precisa reduzir sua árvore de pesquisa o máximo possível.
Itens a serem verificados antes de seguir byte a byte:
Os tamanhos são iguais?
O último byte no arquivo A é diferente do arquivo B
Além disso, a leitura de grandes blocos de cada vez será mais eficiente, pois as unidades leem bytes seqüenciais mais rapidamente. Passar byte a byte causa não apenas muito mais chamadas do sistema, como também faz com que o cabeçote de leitura de um disco rígido tradicional procure e volte com mais frequência se os dois arquivos estiverem na mesma unidade.
Leia o fragmento A e o fragmento B em um buffer de bytes e compare-os (NÃO use Array.Equals, consulte comentários). Ajuste o tamanho dos blocos até atingir o que você acha que é uma boa troca entre memória e desempenho. Você também pode multi-thread a comparação, mas não multi-thread o disco lê.
Usar Array.Equals é uma péssima idéia, pois compara toda a matriz. É provável que pelo menos uma leitura de bloco não preencha toda a matriz.
precisa saber é o seguinte
Por que comparar toda a matriz é uma má ideia? Por que uma leitura de bloco não preenche a matriz? Definitivamente, há um bom ponto de sintonia, mas é por isso que você brinca com os tamanhos. Pontos extras para fazer a comparação em um thread separado.
RandomInsano
Quando você define uma matriz de bytes, ela terá um comprimento fixo. (por exemplo, - var buffer = novo byte [4096]) Quando você lê um bloco do arquivo, ele pode ou não retornar os 4096 bytes completos. Por exemplo, se o arquivo tiver apenas 3000 bytes.
precisa
Ah, agora eu entendo! A boa notícia é que a leitura retornará o número de bytes carregados na matriz; portanto, se a matriz não puder ser preenchida, haverá dados. Como estamos testando a igualdade, os dados antigos do buffer não importam. Docs: msdn.microsoft.com/en-us/library/9kstw824(v=vs.110).aspx
RandomInsano
Também importante, minha recomendação de usar o método Equals () é uma má idéia. Em Mono, eles fazem uma comparação de memória, pois os elementos são contíguos na memória. A Microsoft, no entanto, não a substitui, apenas faz uma comparação de referência que aqui sempre seria falsa.
RandomInsano
4
Minha resposta é derivada de @lars, mas corrige o erro na chamada para Stream.Read. Também adiciono algumas verificações rápidas que outras respostas tiveram e validação de entrada. Em suma, esta deve ser a resposta:
using System;
using System.IO;
namespace ConsoleApp4{classProgram{staticvoidMain(string[] args){var fi1 =newFileInfo(args[0]);var fi2 =newFileInfo(args[1]);Console.WriteLine(FilesContentsAreEqual(fi1, fi2));}publicstaticboolFilesContentsAreEqual(FileInfo fileInfo1,FileInfo fileInfo2){if(fileInfo1 ==null){thrownewArgumentNullException(nameof(fileInfo1));}if(fileInfo2 ==null){thrownewArgumentNullException(nameof(fileInfo2));}if(string.Equals(fileInfo1.FullName, fileInfo2.FullName,StringComparison.OrdinalIgnoreCase)){returntrue;}if(fileInfo1.Length!= fileInfo2.Length){returnfalse;}else{
using (var file1 = fileInfo1.OpenRead()){
using (var file2 = fileInfo2.OpenRead()){returnStreamsContentsAreEqual(file1, file2);}}}}privatestaticintReadFullBuffer(Stream stream,byte[] buffer){int bytesRead =0;while(bytesRead < buffer.Length){int read = stream.Read(buffer, bytesRead, buffer.Length- bytesRead);if(read ==0){// Reached end of stream.return bytesRead;}
bytesRead += read;}return bytesRead;}privatestaticboolStreamsContentsAreEqual(Stream stream1,Stream stream2){constint bufferSize =1024*sizeof(Int64);var buffer1 =newbyte[bufferSize];var buffer2 =newbyte[bufferSize];while(true){int count1 =ReadFullBuffer(stream1, buffer1);int count2 =ReadFullBuffer(stream2, buffer2);if(count1 != count2){returnfalse;}if(count1 ==0){returntrue;}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))){returnfalse;}}}}}}
Ou, se você quiser ser super incrível, use a variante assíncrona:
using System;
using System.IO;
using System.Threading.Tasks;
namespace ConsoleApp4{classProgram{staticvoidMain(string[] args){var fi1 =newFileInfo(args[0]);var fi2 =newFileInfo(args[1]);Console.WriteLine(FilesContentsAreEqualAsync(fi1, fi2).GetAwaiter().GetResult());}publicstaticasyncTask<bool>FilesContentsAreEqualAsync(FileInfo fileInfo1,FileInfo fileInfo2){if(fileInfo1 ==null){thrownewArgumentNullException(nameof(fileInfo1));}if(fileInfo2 ==null){thrownewArgumentNullException(nameof(fileInfo2));}if(string.Equals(fileInfo1.FullName, fileInfo2.FullName,StringComparison.OrdinalIgnoreCase)){returntrue;}if(fileInfo1.Length!= fileInfo2.Length){returnfalse;}else{
using (var file1 = fileInfo1.OpenRead()){
using (var file2 = fileInfo2.OpenRead()){returnawaitStreamsContentsAreEqualAsync(file1, file2).ConfigureAwait(false);}}}}privatestaticasyncTask<int>ReadFullBufferAsync(Stream stream,byte[] buffer){int bytesRead =0;while(bytesRead < buffer.Length){int read =await stream.ReadAsync(buffer, bytesRead, buffer.Length- bytesRead).ConfigureAwait(false);if(read ==0){// Reached end of stream.return bytesRead;}
bytesRead += read;}return bytesRead;}privatestaticasyncTask<bool>StreamsContentsAreEqualAsync(Stream stream1,Stream stream2){constint bufferSize =1024*sizeof(Int64);var buffer1 =newbyte[bufferSize];var buffer2 =newbyte[bufferSize];while(true){int count1 =awaitReadFullBufferAsync(stream1, buffer1).ConfigureAwait(false);int count2 =awaitReadFullBufferAsync(stream2, buffer2).ConfigureAwait(false);if(count1 != count2){returnfalse;}if(count1 ==0){returntrue;}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))){returnfalse;}}}}}}
não seria o bit do conversor de bits melhor que `` for (var i = 0; i <count; i + = sizeof (long)) {if (BitConverter.ToInt64 (buffer1, i)! = BitConverter.ToInt64 (buffer2, i)) { retorna falso; }} `` `
Simon
2
Minhas experiências mostram que definitivamente ajuda a chamar Stream.ReadByte () menos vezes, mas usar o BitConverter para empacotar bytes não faz muita diferença em comparação à comparação de bytes em uma matriz de bytes.
Portanto, é possível substituir o loop "Math.Ceiling e iterações" no comentário acima pelo mais simples:
for(int i =0; i < count1; i++){if(buffer1[i]!= buffer2[i])returnfalse;}
Eu acho que isso tem a ver com o fato de que o BitConverter.ToInt64 precisa fazer um pouco de trabalho (verificar argumentos e depois executar a troca de bits) antes de comparar e isso acaba sendo a mesma quantidade de trabalho que comparar 8 bytes em duas matrizes .
Array.Equals vai mais fundo no sistema, portanto, provavelmente será muito mais rápido que passar byte a byte em C #. Não posso falar pela Microsoft, mas, no fundo, o Mono usa o comando memcpy () de C para obter igualdade de matriz. Não pode ficar muito mais rápido que isso.
RandomInsano 27/01
2
@RandomInsano acho que você memcmp média (), não memcpy ()
A polícia SQL
1
Se os arquivos não forem muito grandes, você pode usar:
publicstaticbyte[]ComputeFileHash(string fileName){
using (var stream =File.OpenRead(fileName))returnSystem.Security.Cryptography.MD5.Create().ComputeHash(stream);}
Só será possível comparar hashes se eles forem úteis para armazenar.
Outra melhoria em arquivos grandes com comprimento idêntico pode ser não ler os arquivos sequencialmente, mas comparar blocos mais ou menos aleatórios.
Você pode usar vários threads, iniciando em diferentes posições no arquivo e comparando para frente ou para trás.
Dessa forma, você pode detectar alterações no meio / final do arquivo, mais rapidamente do que você chegaria lá usando uma abordagem seqüencial.
Unidades de disco físicas sim, os SSDs resolveriam isso.
TheLegendaryCopyCoder
1
Se você precisar comparar apenas dois arquivos, acho que o caminho mais rápido seria (em C, não sei se é aplicável ao .NET)
abra os dois arquivos f1, f2
obter o tamanho do arquivo respectivo l1, l2
se l1! = l2 os arquivos são diferentes; Pare
mmap () ambos os arquivos
use memcmp () nos arquivos mmap () ed
OTOH, se você precisar descobrir se há arquivos duplicados em um conjunto de N arquivos, o caminho mais rápido será, sem dúvida, usar um hash para evitar comparações N-bit bit a bit.
Aqui estão algumas funções utilitárias que permitem determinar se dois arquivos (ou dois fluxos) contêm dados idênticos.
Forneci uma versão "rápida" com vários segmentos, pois compara matrizes de bytes (cada buffer preenchido com o que foi lido em cada arquivo) em diferentes segmentos usando Tarefas.
Como esperado, é muito mais rápido (cerca de 3x mais rápido), mas consome mais CPU (por ser multiencadeado) e mais memória (porque precisa de dois buffers de matriz de bytes por thread de comparação).
publicstaticboolAreFilesIdenticalFast(string path1,string path2){returnAreFilesIdentical(path1, path2,AreStreamsIdenticalFast);}publicstaticboolAreFilesIdentical(string path1,string path2){returnAreFilesIdentical(path1, path2,AreStreamsIdentical);}publicstaticboolAreFilesIdentical(string path1,string path2,Func<Stream,Stream,bool> areStreamsIdentical){if(path1 ==null)thrownewArgumentNullException(nameof(path1));if(path2 ==null)thrownewArgumentNullException(nameof(path2));if(areStreamsIdentical ==null)thrownewArgumentNullException(nameof(path2));if(!File.Exists(path1)||!File.Exists(path2))returnfalse;
using (var thisFile =newFileStream(path1,FileMode.Open,FileAccess.Read,FileShare.ReadWrite)){
using (var valueFile =newFileStream(path2,FileMode.Open,FileAccess.Read,FileShare.ReadWrite)){if(valueFile.Length!= thisFile.Length)returnfalse;if(!areStreamsIdentical(thisFile, valueFile))returnfalse;}}returntrue;}publicstaticboolAreStreamsIdenticalFast(Stream stream1,Stream stream2){if(stream1 ==null)thrownewArgumentNullException(nameof(stream1));if(stream2 ==null)thrownewArgumentNullException(nameof(stream2));constint bufsize =80000;// 80000 is below LOH (85000)var tasks =newList<Task<bool>>();do{// consumes more memory (two buffers for each tasks)var buffer1 =newbyte[bufsize];var buffer2 =newbyte[bufsize];int read1 = stream1.Read(buffer1,0, buffer1.Length);if(read1 ==0){int read3 = stream2.Read(buffer2,0,1);if(read3 !=0)// not eofreturnfalse;break;}// both stream read could return different countsint read2 =0;do{int read3 = stream2.Read(buffer2, read2, read1 - read2);if(read3 ==0)returnfalse;
read2 += read3;}while(read2 < read1);// consumes more cpuvar task =Task.Run(()=>{returnIsSame(buffer1, buffer2);});
tasks.Add(task);}while(true);Task.WaitAll(tasks.ToArray());return!tasks.Any(t =>!t.Result);}publicstaticboolAreStreamsIdentical(Stream stream1,Stream stream2){if(stream1 ==null)thrownewArgumentNullException(nameof(stream1));if(stream2 ==null)thrownewArgumentNullException(nameof(stream2));constint bufsize =80000;// 80000 is below LOH (85000)var buffer1 =newbyte[bufsize];var buffer2 =newbyte[bufsize];var tasks =newList<Task<bool>>();do{int read1 = stream1.Read(buffer1,0, buffer1.Length);if(read1 ==0)return stream2.Read(buffer2,0,1)==0;// check not eof// both stream read could return different countsint read2 =0;do{int read3 = stream2.Read(buffer2, read2, read1 - read2);if(read3 ==0)returnfalse;
read2 += read3;}while(read2 < read1);if(!IsSame(buffer1, buffer2))returnfalse;}while(true);}publicstaticboolIsSame(byte[] bytes1,byte[] bytes2){if(bytes1 ==null)thrownewArgumentNullException(nameof(bytes1));if(bytes2 ==null)thrownewArgumentNullException(nameof(bytes2));if(bytes1.Length!= bytes2.Length)returnfalse;for(int i =0; i < bytes1.Length; i++){if(bytes1[i]!= bytes2[i])returnfalse;}returntrue;}
Eu acho que existem aplicativos em que "hash" é mais rápido do que comparar byte por byte. Se você precisar comparar um arquivo com outras pessoas ou ter uma miniatura de uma foto que possa ser alterada. Depende de onde e como está sendo usado.
Outra resposta, derivada de @chsh. MD5 com utilizações e atalhos para o mesmo arquivo, arquivo não existe e comprimentos diferentes:
/// <summary>/// Performs an md5 on the content of both files and returns true if/// they match/// </summary>/// <param name="file1">first file</param>/// <param name="file2">second file</param>/// <returns>true if the contents of the two files is the same, false otherwise</returns>publicstaticboolIsSameContent(string file1,string file2){if(file1 == file2)returntrue;FileInfo file1Info =newFileInfo(file1);FileInfo file2Info =newFileInfo(file2);if(!file1Info.Exists&&!file2Info.Exists)returntrue;if(!file1Info.Exists&& file2Info.Exists)returnfalse;if(file1Info.Exists&&!file2Info.Exists)returnfalse;if(file1Info.Length!= file2Info.Length)returnfalse;
using (FileStream file1Stream = file1Info.OpenRead())
using (FileStream file2Stream = file2Info.OpenRead()){byte[] firstHash = MD5.Create().ComputeHash(file1Stream);byte[] secondHash = MD5.Create().ComputeHash(file2Stream);for(int i =0; i < firstHash.Length; i++){if(i>=secondHash.Length||firstHash[i]!= secondHash[i])returnfalse;}returntrue;}}
Respostas:
Uma comparação de soma de verificação provavelmente será mais lenta que uma comparação de byte a byte.
Para gerar uma soma de verificação, você precisará carregar cada byte do arquivo e executar o processamento nele. Você precisará fazer isso no segundo arquivo. Definitivamente, o processamento será mais lento que a verificação de comparação.
Quanto à geração de uma soma de verificação: você pode fazer isso facilmente com as classes de criptografia. Aqui está um pequeno exemplo de como gerar uma soma de verificação MD5 com C #.
No entanto, uma soma de verificação pode ser mais rápida e fazer mais sentido se você puder pré-calcular a soma de verificação do caso "teste" ou "base". Se você possui um arquivo existente e está verificando se um novo arquivo é igual ao existente, pré-calcular a soma de verificação no arquivo "existente" significaria apenas a necessidade de fazer o DiskIO uma vez, no diretório novo arquivo. Isso provavelmente seria mais rápido que uma comparação de byte a byte.
fonte
O método mais lento possível é comparar dois arquivos byte a byte. O mais rápido que consegui fazer é uma comparação semelhante, mas, em vez de um byte por vez, você usaria uma matriz de bytes dimensionada para Int64 e, em seguida, compararia os números resultantes.
Aqui está o que eu vim com:
Nos meus testes, eu pude ver isso superar um cenário simples de ReadByte () em quase 3: 1. Em média, mais de 1000 execuções, obtive esse método em 1063ms e o método abaixo (comparação direta de bytes por byte) em 3031ms. O hash sempre voltava em um segundo em torno de uma média de 865ms. Este teste foi realizado com um arquivo de vídeo de ~ 100 MB.
Aqui estão os métodos ReadByte e hash que eu usei, para fins de comparação:
fonte
FilesAreEqual_Hash
método deve ter umusing
nos dois fluxos de arquivos também como oReadByte
método, caso contrário, ele permanecerá nos dois arquivos.FileStream.Read()
na verdade, pode ler menos bytes que o número solicitado. Você deve usar em seuStreamReader.ReadBlock()
lugar.Se você não decidir você realmente precisa de uma comparação completa byte a byte (veja outras respostas para a discussão de hashing), então a solução mais fácil é:
• para
System.IO.FileInfo
instâncias:• para
System.String
nomes de caminhos:Diferentemente de outras respostas publicadas, isso é conclusivamente correto para qualquer tipo de arquivo: binário, texto, mídia, executável etc., mas como uma comparação binária completa , arquivos que diferem apenas de maneiras "sem importância" (como BOM , linha -ending , codificação de caracteres , metadados de mídia, espaço em branco, estofamento, comentários de código-fonte, etc.) será sempre considerado não-igual .
Esse código carrega os dois arquivos inteiramente na memória, portanto, não deve ser usado para comparar arquivos verdadeiramente gigantescos . Além dessa ressalva importante, o carregamento completo não é realmente uma penalidade, devido ao design do .NET GC (porque é fundamentalmente otimizado para manter alocações pequenas e de curta duração extremamente baratas ) e, de fato, pode ser ideal quando os tamanhos de arquivo são esperados a ser menos do que 85K , porque usar um mínimo de código de utilizador (como mostrado aqui) implica maximamente delegar problemas de desempenho arquivo para o
CLR
,BCL
eJIT
para benefício de (por exemplo) o mais recente projeto, código do sistema, e otimizações de tempo de execução adaptativos.Além disso, para esses cenários de trabalho, preocupações com o desempenho da comparação de byte a byte através de
LINQ
enumeradores (como mostrado aqui) são discutíveis, já que bater no disco a̲t̲ a̲l̲l̲ para E / S de arquivo diminui, por várias ordens de magnitude, os benefícios das várias alternativas de comparação de memória. Por exemplo, emboraSequenceEqual
realmente nos dê a "otimização" de abandonar na primeira incompatibilidade , isso dificilmente importa depois de já ter buscado o conteúdo dos arquivos, cada um totalmente necessário para confirmar a correspondência.fonte
Além da resposta de Reed Copsey :
O pior caso é onde os dois arquivos são idênticos. Nesse caso, é melhor comparar os arquivos byte a byte.
Se os dois arquivos não forem idênticos, você poderá acelerar um pouco as coisas, detectando antes que eles não são idênticos.
Por exemplo, se os dois arquivos tiverem tamanho diferente, você saberá que eles não podem ser idênticos e nem precisará comparar o conteúdo real.
fonte
Está ficando ainda mais rápido se você não ler em pequenos pedaços de 8 bytes, mas fizer um loop, lendo um pedaço maior. Reduzi o tempo médio de comparação para 1/4.
fonte
count1 != count2
não está correta.Stream.Read()
pode retornar menos do que a contagem que você forneceu, por vários motivos.Int64
blocos, você pode querer calcular o tamanho da seguinte forma:const int bufferSize = 1024 * sizeof(Int64)
.A única coisa que pode fazer uma comparação de soma de verificação um pouco mais rápida que uma comparação de byte a byte é o fato de você estar lendo um arquivo de cada vez, reduzindo um pouco o tempo de busca da cabeça do disco. Esse pequeno ganho pode, no entanto, muito bem ser consumido pelo tempo adicional de cálculo do hash.
Além disso, é claro que uma comparação de soma de verificação só tem chance de ser mais rápida se os arquivos forem idênticos. Caso contrário, uma comparação de byte a byte terminaria na primeira diferença, tornando-a muito mais rápida.
Você também deve considerar que uma comparação de código de hash apenas informa que é muito provável que os arquivos sejam idênticos. Para ter 100% de certeza, você precisa fazer uma comparação byte a byte.
Se o código de hash, por exemplo, for de 32 bits, você terá 99,99999998% de certeza de que os arquivos serão idênticos se os códigos de hash corresponderem. Isso é quase 100%, mas se você realmente precisa de 100% de certeza, não é isso.
fonte
1 - (1 / (2^32))
, que é a probabilidade de que um único arquivo tenha um hash de 32 bits. A probabilidade de dois arquivos diferentes terem o mesmo hash é a mesma, porque o primeiro arquivo fornece o valor de hash "fornecido" e precisamos considerar apenas se o outro arquivo corresponde ou não a esse valor. As chances de hash de 64 e 128 bits diminuem para 99,999999999999999994% e 99,99999999999999999999999999999999997% (respectivamente), como se isso importasse com esses números insondáveis.Edit: Este método não funcionaria para comparar arquivos binários!
No .NET 4.0, a
File
classe possui os dois novos métodos a seguir:O que significa que você pode usar:
fonte
Honestamente, acho que você precisa reduzir sua árvore de pesquisa o máximo possível.
Itens a serem verificados antes de seguir byte a byte:
Além disso, a leitura de grandes blocos de cada vez será mais eficiente, pois as unidades leem bytes seqüenciais mais rapidamente. Passar byte a byte causa não apenas muito mais chamadas do sistema, como também faz com que o cabeçote de leitura de um disco rígido tradicional procure e volte com mais frequência se os dois arquivos estiverem na mesma unidade.
Leia o fragmento A e o fragmento B em um buffer de bytes e compare-os (NÃO use Array.Equals, consulte comentários). Ajuste o tamanho dos blocos até atingir o que você acha que é uma boa troca entre memória e desempenho. Você também pode multi-thread a comparação, mas não multi-thread o disco lê.
fonte
Minha resposta é derivada de @lars, mas corrige o erro na chamada para
Stream.Read
. Também adiciono algumas verificações rápidas que outras respostas tiveram e validação de entrada. Em suma, esta deve ser a resposta:Ou, se você quiser ser super incrível, use a variante assíncrona:
fonte
Minhas experiências mostram que definitivamente ajuda a chamar Stream.ReadByte () menos vezes, mas usar o BitConverter para empacotar bytes não faz muita diferença em comparação à comparação de bytes em uma matriz de bytes.
Portanto, é possível substituir o loop "Math.Ceiling e iterações" no comentário acima pelo mais simples:
Eu acho que isso tem a ver com o fato de que o BitConverter.ToInt64 precisa fazer um pouco de trabalho (verificar argumentos e depois executar a troca de bits) antes de comparar e isso acaba sendo a mesma quantidade de trabalho que comparar 8 bytes em duas matrizes .
fonte
Se os arquivos não forem muito grandes, você pode usar:
Só será possível comparar hashes se eles forem úteis para armazenar.
(Editou o código para algo muito mais limpo.)
fonte
Outra melhoria em arquivos grandes com comprimento idêntico pode ser não ler os arquivos sequencialmente, mas comparar blocos mais ou menos aleatórios.
Você pode usar vários threads, iniciando em diferentes posições no arquivo e comparando para frente ou para trás.
Dessa forma, você pode detectar alterações no meio / final do arquivo, mais rapidamente do que você chegaria lá usando uma abordagem seqüencial.
fonte
Se você precisar comparar apenas dois arquivos, acho que o caminho mais rápido seria (em C, não sei se é aplicável ao .NET)
OTOH, se você precisar descobrir se há arquivos duplicados em um conjunto de N arquivos, o caminho mais rápido será, sem dúvida, usar um hash para evitar comparações N-bit bit a bit.
fonte
Algo (espero) razoavelmente eficiente:
fonte
Aqui estão algumas funções utilitárias que permitem determinar se dois arquivos (ou dois fluxos) contêm dados idênticos.
Forneci uma versão "rápida" com vários segmentos, pois compara matrizes de bytes (cada buffer preenchido com o que foi lido em cada arquivo) em diferentes segmentos usando Tarefas.
Como esperado, é muito mais rápido (cerca de 3x mais rápido), mas consome mais CPU (por ser multiencadeado) e mais memória (porque precisa de dois buffers de matriz de bytes por thread de comparação).
fonte
Eu acho que existem aplicativos em que "hash" é mais rápido do que comparar byte por byte. Se você precisar comparar um arquivo com outras pessoas ou ter uma miniatura de uma foto que possa ser alterada. Depende de onde e como está sendo usado.
Aqui, você pode obter o que é mais rápido.
Opcionalmente, podemos salvar o hash em um banco de dados.
Espero que isso possa ajudar
fonte
Outra resposta, derivada de @chsh. MD5 com utilizações e atalhos para o mesmo arquivo, arquivo não existe e comprimentos diferentes:
fonte
if (i>=secondHash.Length ...
sob que circunstâncias dois hashes MD5 teriam comprimentos diferentes?Descobri que funciona bem comparando primeiro o comprimento sem ler os dados e, em seguida, comparando a sequência de bytes lidos
fonte