Сравнение контрольной суммы, скорее всего, будет медленнее, чем побайтовое сравнение.
Чтобы сгенерировать контрольную сумму, вам нужно загрузить каждый байт файла и выполнить его обработку. Затем вам нужно будет сделать это на втором файле. Обработка почти наверняка будет медленнее, чем проверка сравнения.
Тем не менее, контрольная сумма может быть быстрее и имеет больше смысла, если вы можете предварительно вычислить контрольную сумму «тестового» или «базового» случая. Если у вас есть существующий файл, и вы проверяете, совпадает ли новый файл с существующим, предварительное вычисление контрольной суммы в вашем «существующем» файле будет означать, что DiskIO потребуется только один раз, новый файл. Это, вероятно, будет быстрее, чем побайтовое сравнение.
Обязательно примите во внимание, где находятся ваши файлы. Если вы сравниваете локальные файлы с резервной копией на полпути по всему миру (или по сети с ужасной пропускной способностью), вам может быть лучше сначала хешировать и отправлять контрольную сумму по сети, а не посылать поток байтов сравнить.
Ким
@ReedCopsey: У меня похожая проблема, так как мне нужно хранить входные / выходные файлы, созданные несколькими разработками, которые должны содержать много дубликатов. Я думал использовать предварительно вычисленный хеш, но вы думаете, что я могу разумно предположить, что если 2 (например, MD5) хеша равны, 2 файла равны и избегают дальнейшего байтового 2-байтового сравнения? Насколько я знаю, столкновения MD5 / SHA1 и т. Д. Действительно маловероятны ...
digEmAll
1
@digEmAll Вероятность столкновения низка - вы всегда можете сделать более сильный хеш - то есть: используйте SHA256 вместо SHA1, что еще больше уменьшит вероятность столкновений.
Рид Копси
спасибо за ваш ответ - я просто вхожу в .net. Я предполагаю, что если кто-то использует технику хэш-кода / контрольной суммы, то где-то будут постоянно храниться хеши основной папки? из любопытства, как бы вы сохранили его для приложения WPF - что бы вы сделали? (В настоящее время я смотрю на XML, текстовые файлы или базы данных).
BKSpurgeon
139
Самый медленный способ - это сравнить два файла побайтно. Самое быстрое, что я смог придумать, - это аналогичное сравнение, но вместо одного байта за раз вы бы использовали массив байтов размером в Int64, а затем сравнили полученные числа.
Вот что я придумал:
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;}
В моем тестировании я смог увидеть, как это превзошло простой сценарий ReadByte () почти в 3: 1. В среднем за 1000 прогонов я получил этот метод на 1063 мс, а метод ниже (прямое побайтное сравнение) на 3031 мс. Хэширование всегда возвращалось на долю секунды ниже среднего значения в 865 мс. Это тестирование проводилось с видеофайлом ~ 100 МБ.
Вот методы ReadByte и хэширования, которые я использовал для сравнения:
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;}
FilesAreEqual_HashМетод должен иметь usingна обоих потоков файла тоже как ReadByteметод в противном случае он будет висеть на обоих файлах.
Ян Мерсер
2
Обратите внимание, что на FileStream.Read()самом деле может быть прочитано меньше байтов, чем запрошенное число. Вы должны использовать StreamReader.ReadBlock()вместо этого.
Palec
2
В версии Int64, когда длина потока не кратна Int64, последняя итерация сравнивает незаполненные байты, используя заполнение предыдущей итерации (которое также должно быть равно, так что это нормально). Также, если длина потока меньше sizeof (Int64), то незаполненные байты равны 0, поскольку C # инициализирует массивы. ИМО, код, вероятно, должен комментировать эти странности.
crokusek
46
Если вы действительно решите , что вам действительно нужен полный байт в байт сравнения (см другие ответы для обсуждения хеширования), то самым простым решением является:
В отличие от некоторых других опубликованных ответов, это абсолютно верно для любого типа файла: двоичного, текстового, мультимедийного, исполняемого и т. Д., Но в качестве полного двоичного сравнения - файлов, которые отличаются только «неважными» способами (такими как спецификация , строка окончание , кодировка символов , медиа-метаданные, пробелы, отступы, комментарии исходного кода и т. д.) всегда будут считаться неравными .
Этот код полностью загружает оба файла в память, поэтому его не следует использовать для сравнения действительно гигантских файлов . Помимо этого важного предостережения, полная загрузка на самом деле не является штрафом, учитывая дизайн .NET GC (потому что он принципиально оптимизирован, чтобы сохранять небольшие, недолговечные выделения очень дешевыми ), и на самом деле может даже быть оптимальным, когда ожидаются размеры файлов быть меньше , чем 8кИ , потому что , используя минимум кода пользователя (как показано здесь) предполагает максимально делегирование проблемы производительности файла в CLR, BCLи JITна пользу от (например) по последнему слову техники проектирования, системный кода, а также адаптивной оптимизации времени выполнения.
Кроме того, для таких рабочих сценариев озабоченность по поводу производительности побайтового сравнения через LINQперечислители (как показано здесь) не имеет значения, поскольку попадание диска a̲t̲ a̲l̲l̲ для файлового ввода-вывода приведет к затуханию на несколько порядков преимуществ, из различных альтернатив сравнения памяти. Например, даже если SequenceEqualэто на самом деле дает нам «оптимизацию» отказ от первого несовпадения , это не имеет особое значение после того , как уже принесли содержание файлов, каждый полностью необходимо подтвердить совпадение ..
этот не выглядит хорошо для больших файлов. не подходит для использования памяти, так как он прочитает оба файла до конца перед началом сравнения байтового массива. Вот почему я предпочел бы пойти для потокового чтения с буфером.
Krypto_47
3
@ Krypto_47 Я обсуждал эти факторы и примерное использование в тексте моего ответа.
Гленн
33
В дополнение к ответу Рида Копси :
Худший случай, когда два файла идентичны. В этом случае лучше сравнить файлы побайтно.
Если два файла не идентичны, вы можете немного ускорить процесс, обнаружив, что они не идентичны.
Например, если два файла имеют разную длину, вы знаете, что они не могут быть идентичными, и вам даже не нужно сравнивать их фактическое содержимое.
Для завершения: другой большой выигрыш останавливается, как только байты в 1 позиции различаются.
Хенк Холтерман
6
@Henk: я думал, что это было слишком очевидно :-)
дтб
1
Хороший момент при добавлении этого. Это было очевидно для меня, поэтому я не включил это, но это хорошо, чтобы упомянуть.
Рид Копси
16
Это становится еще быстрее, если вы не читаете небольшие 8-байтовые блоки, а просто делаете цикл, читая больший фрагмент. Я сократил среднее время сравнения до 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;}}}}}
В общем, проверка count1 != count2не верна. Stream.Read()может вернуть меньше, чем количество, которое вы предоставили, по разным причинам.
Porges
1
Для того, чтобы гарантировать , что буфер будет держать четное число Int64блоков, вы можете вычислить размер , как это: const int bufferSize = 1024 * sizeof(Int64).
Джек А.
14
Единственное, что может сделать сравнение контрольной суммы немного быстрее, чем побайтовое сравнение, - это то, что вы читаете один файл за раз, что несколько сокращает время поиска для головки диска. Этот небольшой выигрыш, однако, вполне может быть поглощен дополнительным временем вычисления хэша.
Кроме того, сравнение контрольной суммы, конечно, имеет шанс быть быстрее, если файлы идентичны. Если это не так, побайтовое сравнение заканчивается первым различием, что делает его намного быстрее.
Вам также следует учитывать, что сравнение хеш-кода говорит только о том, что файлы, скорее всего , идентичны. Чтобы быть на 100% уверенным, вам нужно сделать побайтовое сравнение.
Например, если хэш-код равен 32 битам, вы примерно на 99,9999999% уверены, что файлы идентичны, если хэш-коды совпадают. Это близко к 100%, но если вам действительно нужна 100% уверенность, это не так.
Используйте больший хэш, и вы можете получить вероятность ложного срабатывания намного ниже вероятности ошибки компьютера во время теста.
Лорен Печтель
Я не согласен по поводу времени хеширования против времени поиска. Вы можете сделать много вычислений во время одного поиска головы. Если вероятность того, что файлы совпадают, высока, я бы использовал хэш с большим количеством битов. Если есть вероятность совпадения, я бы сравнивал их по одному блоку, например, по 1 МБ. (Выберите размер блока, который 4k делит равномерно, чтобы гарантировать, что вы никогда не разделите секторы.)
Loren Pechtel
1
Чтобы объяснить цифру @ Guffa 99.99999998%, она исходит из вычислений 1 - (1 / (2^32)), то есть вероятности того, что любой отдельный файл будет иметь определенный 32-битный хэш. Вероятность того, что два разных файла имеют одинаковый хэш, одинакова, поскольку первый файл предоставляет «заданное» значение хеш-функции, и нам нужно только рассмотреть, соответствует ли другой файл этому значению. Шансы с 64- и 128-битным хешированием уменьшаются до 99,9999999999999999994% и 99,999999999999999999999999999999999997% (соответственно), как будто это имеет значение с такими непостижимыми числами.
Гленн Слэйден
... Действительно, тот факт, что большинству людей эти цифры труднее понять, чем предположительно простое, хотя и истинное понятие «бесконечно много файлов, сталкивающихся в один и тот же хэш-код», может объяснить, почему люди необоснованно с подозрением относятся к принятию хеш- кода как равенство.
Гленн
13
Изменить: этот метод не будет работать для сравнения двоичных файлов!
В .NET 4.0 Fileкласс имеет следующие два новых метода:
@dtb: это не работает для двоичных файлов. Вы, наверное, уже печатали комментарий, когда я понял это и добавил правку вверху моего поста. : o
Сэм Харвелл
@ 280Z28: Я ничего не сказал ;-)
DTB
Разве вам не нужно хранить оба файла в памяти?
RandomInsano
Обратите внимание, что File также имеет функцию ReadAllBytes, которая также может использовать SequenceEquals, поэтому используйте ее вместо этого, поскольку она будет работать со всеми файлами. И, как сказал @RandomInsano, он хранится в памяти, поэтому, хотя он прекрасно подходит для небольших файлов, я бы осторожно использовал его для больших файлов.
DaedalusAlpha
1
@DaedalusAlpha Возвращает перечисляемое значение, поэтому строки будут загружаться по требованию и не сохраняться в памяти все время. ReadAllBytes, с другой стороны, возвращает весь файл в виде массива.
IllidanS4 хочет, чтобы Моника вернулась
7
Честно говоря, я думаю, вам нужно как можно меньше обрезать свое дерево поиска.
Что нужно проверить перед переходом побайтно:
Размеры одинаковы?
Последний байт в файле A отличается от файла B
Кроме того, одновременное чтение больших блоков будет более эффективным, поскольку диски читают последовательные байты быстрее. Байт-байт приводит не только к большему количеству системных вызовов, но и к тому, что головка чтения традиционного жесткого диска выполняет поиск назад и вперед чаще, если оба файла находятся на одном диске.
Считайте порцию A и порцию B в байтовый буфер и сравните их (НЕ используйте Array.Equals, смотрите комментарии). Настраивайте размер блоков, пока не добьетесь того, что вы считаете хорошим компромиссом между памятью и производительностью. Вы также можете многопоточное сравнение, но не многопоточное чтение диска.
Использование Array.Equals - плохая идея, потому что он сравнивает весь массив. Вполне вероятно, что хотя бы одно чтение блока не заполнит весь массив.
Даг Клаттер
Почему сравнение всего массива плохая идея? Почему чтение блока не заполняет массив? Определенно есть хорошая настройка, но именно поэтому вы играете с размерами. Дополнительные баллы для проведения сравнения в отдельном потоке.
RandomInsano
Когда вы определяете байтовый массив, он будет иметь фиксированную длину. (например: var buffer = new byte [4096]) Когда вы читаете блок из файла, он может возвращать или не возвращать полные 4096 байтов. Например, если файл имеет длину всего 3000 байт.
Даг Клаттер
Ах, теперь я понимаю! Хорошей новостью является то, что чтение вернет количество байтов, загруженных в массив, поэтому, если массив не может быть заполнен, будут данные. Поскольку мы проверяем на равенство, старые данные буфера не будут иметь значения. Документы: msdn.microsoft.com/en-us/library/9kstw824(v=vs.110).aspx
RandomInsano
Также важно, что моя рекомендация использовать метод Equals () - плохая идея. В Mono они сравнивают память, так как элементы являются смежными в памяти. Microsoft, однако, не отменяет его, а только делает сравнение ссылок, которое здесь всегда будет ложным.
RandomInsano
4
Мой ответ является производным от @lars, но исправляет ошибку в вызове Stream.Read. Я также добавил быструю проверку пути, которую имели другие ответы, и проверку ввода. Короче говоря, это должно быть ответ:
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;}}}}}}
Или, если вы хотите быть супер-классным, вы можете использовать асинхронный вариант:
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;}}}}}}
не будет ли битовый преобразователь лучше, чем `` `for (var i = 0; i <count; i + = sizeof (long)) {if (BitConverter.ToInt64 (buffer1, i)! = BitConverter.ToInt64 (buffer2, i)) {вернуть ложь; }} `` `
Симон
2
Мои эксперименты показывают, что это определенно помогает вызывать Stream.ReadByte () меньше раз, но использование BitConverter для упаковки байтов не имеет большого значения по сравнению со сравнением байтов в байтовом массиве.
Таким образом, можно заменить этот цикл "Math.Ceiling and iterations" в комментарии выше на самый простой:
for(int i =0; i < count1; i++){if(buffer1[i]!= buffer2[i])returnfalse;}
Я предполагаю, что это связано с тем фактом, что BitConverter.ToInt64 должен выполнить небольшую работу (проверить аргументы, а затем выполнить сдвиг битов) перед тем, как вы сравните, и это закончится тем же объемом работы, что и сравнение 8 байтов в двух массивах. ,
Array.Equals идет глубже в систему, поэтому, скорее всего, будет намного быстрее, чем проходить побайтно в C #. Я не могу говорить за Microsoft, но в глубине души Mono использует команду C memcpy () для равенства массивов. Не может быть намного быстрее, чем это.
RandomInsano
2
@RandomInsano Полагаю, вы имеете в виду memcmp (), а не memcpy ()
SQL Police
1
Если файлы не слишком большие, вы можете использовать:
publicstaticbyte[]ComputeFileHash(string fileName){
using (var stream =File.OpenRead(fileName))returnSystem.Security.Cryptography.MD5.Create().ComputeHash(stream);}
Сравнивать хэши можно только в том случае, если они полезны для хранения.
Если вам нужно сравнить только два файла, я думаю, что самый быстрый способ будет (в C, я не знаю, применимо ли это к .NET)
откройте оба файла f1, f2
получить соответствующую длину файла l1, l2
если l1! = l2 файлы разные; стоп
mmap () оба файла
используйте memcmp () для файлов mmap ()
OTOH, если вам нужно найти, есть ли дубликаты файлов в наборе из N файлов, то, несомненно, самый быстрый способ - это использование хэша, чтобы избежать побитового сравнения N-way.
Вот некоторые служебные функции, которые позволяют вам определить, содержат ли два файла (или два потока) идентичные данные.
Я предоставил «быструю» версию, которая является многопоточной, поскольку она сравнивает байтовые массивы (каждый буфер заполняется из того, что было прочитано в каждом файле) в разных потоках, используя Задачи.
Как и ожидалось, он намного быстрее (примерно в 3 раза быстрее), но потребляет больше ЦП (потому что он многопоточный) и больше памяти (потому что для каждого потока сравнения требуется два буфера массива байтов).
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;}
Я думаю, что есть приложения, где "хэш" быстрее, чем сравнение байтов за байтом. Если вам нужно сравнить файл с другими или иметь эскиз фотографии, которая может измениться. Это зависит от того, где и как оно используется.
Еще один ответ, полученный из @chsh. MD5 с использованием и ярлыками для файла одного и того же, файл не существует и разной длины:
/// <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;}}
Ответы:
Сравнение контрольной суммы, скорее всего, будет медленнее, чем побайтовое сравнение.
Чтобы сгенерировать контрольную сумму, вам нужно загрузить каждый байт файла и выполнить его обработку. Затем вам нужно будет сделать это на втором файле. Обработка почти наверняка будет медленнее, чем проверка сравнения.
Что касается генерации контрольной суммы: Вы можете легко сделать это с помощью классов криптографии. Вот краткий пример генерации контрольной суммы MD5 с помощью C #.
Тем не менее, контрольная сумма может быть быстрее и имеет больше смысла, если вы можете предварительно вычислить контрольную сумму «тестового» или «базового» случая. Если у вас есть существующий файл, и вы проверяете, совпадает ли новый файл с существующим, предварительное вычисление контрольной суммы в вашем «существующем» файле будет означать, что DiskIO потребуется только один раз, новый файл. Это, вероятно, будет быстрее, чем побайтовое сравнение.
источник
Самый медленный способ - это сравнить два файла побайтно. Самое быстрое, что я смог придумать, - это аналогичное сравнение, но вместо одного байта за раз вы бы использовали массив байтов размером в Int64, а затем сравнили полученные числа.
Вот что я придумал:
В моем тестировании я смог увидеть, как это превзошло простой сценарий ReadByte () почти в 3: 1. В среднем за 1000 прогонов я получил этот метод на 1063 мс, а метод ниже (прямое побайтное сравнение) на 3031 мс. Хэширование всегда возвращалось на долю секунды ниже среднего значения в 865 мс. Это тестирование проводилось с видеофайлом ~ 100 МБ.
Вот методы ReadByte и хэширования, которые я использовал для сравнения:
источник
FilesAreEqual_Hash
Метод должен иметьusing
на обоих потоков файла тоже какReadByte
метод в противном случае он будет висеть на обоих файлах.FileStream.Read()
самом деле может быть прочитано меньше байтов, чем запрошенное число. Вы должны использоватьStreamReader.ReadBlock()
вместо этого.Если вы действительно решите , что вам действительно нужен полный байт в байт сравнения (см другие ответы для обсуждения хеширования), то самым простым решением является:
• для
System.IO.FileInfo
случаев:• для
System.String
имен путей:В отличие от некоторых других опубликованных ответов, это абсолютно верно для любого типа файла: двоичного, текстового, мультимедийного, исполняемого и т. Д., Но в качестве полного двоичного сравнения - файлов, которые отличаются только «неважными» способами (такими как спецификация , строка окончание , кодировка символов , медиа-метаданные, пробелы, отступы, комментарии исходного кода и т. д.) всегда будут считаться неравными .
Этот код полностью загружает оба файла в память, поэтому его не следует использовать для сравнения действительно гигантских файлов . Помимо этого важного предостережения, полная загрузка на самом деле не является штрафом, учитывая дизайн .NET GC (потому что он принципиально оптимизирован, чтобы сохранять небольшие, недолговечные выделения очень дешевыми ), и на самом деле может даже быть оптимальным, когда ожидаются размеры файлов быть меньше , чем 8кИ , потому что , используя минимум кода пользователя (как показано здесь) предполагает максимально делегирование проблемы производительности файла в
CLR
,BCL
иJIT
на пользу от (например) по последнему слову техники проектирования, системный кода, а также адаптивной оптимизации времени выполнения.Кроме того, для таких рабочих сценариев озабоченность по поводу производительности побайтового сравнения через
LINQ
перечислители (как показано здесь) не имеет значения, поскольку попадание диска a̲t̲ a̲l̲l̲ для файлового ввода-вывода приведет к затуханию на несколько порядков преимуществ, из различных альтернатив сравнения памяти. Например, даже еслиSequenceEqual
это на самом деле дает нам «оптимизацию» отказ от первого несовпадения , это не имеет особое значение после того , как уже принесли содержание файлов, каждый полностью необходимо подтвердить совпадение ..источник
В дополнение к ответу Рида Копси :
Худший случай, когда два файла идентичны. В этом случае лучше сравнить файлы побайтно.
Если два файла не идентичны, вы можете немного ускорить процесс, обнаружив, что они не идентичны.
Например, если два файла имеют разную длину, вы знаете, что они не могут быть идентичными, и вам даже не нужно сравнивать их фактическое содержимое.
источник
Это становится еще быстрее, если вы не читаете небольшие 8-байтовые блоки, а просто делаете цикл, читая больший фрагмент. Я сократил среднее время сравнения до 1/4.
источник
count1 != count2
не верна.Stream.Read()
может вернуть меньше, чем количество, которое вы предоставили, по разным причинам.Int64
блоков, вы можете вычислить размер , как это:const int bufferSize = 1024 * sizeof(Int64)
.Единственное, что может сделать сравнение контрольной суммы немного быстрее, чем побайтовое сравнение, - это то, что вы читаете один файл за раз, что несколько сокращает время поиска для головки диска. Этот небольшой выигрыш, однако, вполне может быть поглощен дополнительным временем вычисления хэша.
Кроме того, сравнение контрольной суммы, конечно, имеет шанс быть быстрее, если файлы идентичны. Если это не так, побайтовое сравнение заканчивается первым различием, что делает его намного быстрее.
Вам также следует учитывать, что сравнение хеш-кода говорит только о том, что файлы, скорее всего , идентичны. Чтобы быть на 100% уверенным, вам нужно сделать побайтовое сравнение.
Например, если хэш-код равен 32 битам, вы примерно на 99,9999999% уверены, что файлы идентичны, если хэш-коды совпадают. Это близко к 100%, но если вам действительно нужна 100% уверенность, это не так.
источник
1 - (1 / (2^32))
, то есть вероятности того, что любой отдельный файл будет иметь определенный 32-битный хэш. Вероятность того, что два разных файла имеют одинаковый хэш, одинакова, поскольку первый файл предоставляет «заданное» значение хеш-функции, и нам нужно только рассмотреть, соответствует ли другой файл этому значению. Шансы с 64- и 128-битным хешированием уменьшаются до 99,9999999999999999994% и 99,999999999999999999999999999999999997% (соответственно), как будто это имеет значение с такими непостижимыми числами.Изменить: этот метод не будет работать для сравнения двоичных файлов!
В .NET 4.0
File
класс имеет следующие два новых метода:Что означает, что вы можете использовать:
источник
Честно говоря, я думаю, вам нужно как можно меньше обрезать свое дерево поиска.
Что нужно проверить перед переходом побайтно:
Кроме того, одновременное чтение больших блоков будет более эффективным, поскольку диски читают последовательные байты быстрее. Байт-байт приводит не только к большему количеству системных вызовов, но и к тому, что головка чтения традиционного жесткого диска выполняет поиск назад и вперед чаще, если оба файла находятся на одном диске.
Считайте порцию A и порцию B в байтовый буфер и сравните их (НЕ используйте Array.Equals, смотрите комментарии). Настраивайте размер блоков, пока не добьетесь того, что вы считаете хорошим компромиссом между памятью и производительностью. Вы также можете многопоточное сравнение, но не многопоточное чтение диска.
источник
Мой ответ является производным от @lars, но исправляет ошибку в вызове
Stream.Read
. Я также добавил быструю проверку пути, которую имели другие ответы, и проверку ввода. Короче говоря, это должно быть ответ:Или, если вы хотите быть супер-классным, вы можете использовать асинхронный вариант:
источник
Мои эксперименты показывают, что это определенно помогает вызывать Stream.ReadByte () меньше раз, но использование BitConverter для упаковки байтов не имеет большого значения по сравнению со сравнением байтов в байтовом массиве.
Таким образом, можно заменить этот цикл "Math.Ceiling and iterations" в комментарии выше на самый простой:
Я предполагаю, что это связано с тем фактом, что BitConverter.ToInt64 должен выполнить небольшую работу (проверить аргументы, а затем выполнить сдвиг битов) перед тем, как вы сравните, и это закончится тем же объемом работы, что и сравнение 8 байтов в двух массивах. ,
источник
Если файлы не слишком большие, вы можете использовать:
Сравнивать хэши можно только в том случае, если они полезны для хранения.
(Отредактировал код до чего-то более чистого.)
источник
Еще одним улучшением для больших файлов одинаковой длины может быть не последовательное чтение файлов, а сравнение более или менее случайных блоков.
Вы можете использовать несколько потоков, начиная с разных позиций в файле и сравнивая их вперед или назад.
Таким образом, вы можете обнаружить изменения в середине / конце файла, быстрее, чем при последовательном подходе.
источник
Если вам нужно сравнить только два файла, я думаю, что самый быстрый способ будет (в C, я не знаю, применимо ли это к .NET)
OTOH, если вам нужно найти, есть ли дубликаты файлов в наборе из N файлов, то, несомненно, самый быстрый способ - это использование хэша, чтобы избежать побитового сравнения N-way.
источник
Что-то (надеюсь) достаточно эффективное:
источник
Вот некоторые служебные функции, которые позволяют вам определить, содержат ли два файла (или два потока) идентичные данные.
Я предоставил «быструю» версию, которая является многопоточной, поскольку она сравнивает байтовые массивы (каждый буфер заполняется из того, что было прочитано в каждом файле) в разных потоках, используя Задачи.
Как и ожидалось, он намного быстрее (примерно в 3 раза быстрее), но потребляет больше ЦП (потому что он многопоточный) и больше памяти (потому что для каждого потока сравнения требуется два буфера массива байтов).
источник
Я думаю, что есть приложения, где "хэш" быстрее, чем сравнение байтов за байтом. Если вам нужно сравнить файл с другими или иметь эскиз фотографии, которая может измениться. Это зависит от того, где и как оно используется.
Здесь вы можете получить то, что является самым быстрым.
При желании мы можем сохранить хеш в базе данных.
Надеюсь, что это может помочь
источник
Еще один ответ, полученный из @chsh. MD5 с использованием и ярлыками для файла одного и того же, файл не существует и разной длины:
источник
if (i>=secondHash.Length ...
при каких обстоятельствах два хэша MD5 будут разной длины?Я нашел, что это хорошо работает, сравнивая сначала длину без чтения данных, а затем сравнивая прочитанную последовательность байтов
источник