Как быстро сравнить 2 файла с помощью .NET?

Типичные подходы рекомендуют читать двоичный файл через FileStream и сравнивать его побайтно.

  • Будет ли сравнение контрольных сумм, такое как CRC, быстрее?
  • Существуют ли библиотеки .NET, которые могут генерировать контрольную сумму для файла?

Ответ 1

Сравнение контрольных сумм, скорее всего, будет медленнее, чем сравнение побайтов.

Чтобы создать контрольную сумму, вам необходимо загрузить каждый байт файла и выполнить обработку на нем. Затем вы должны сделать это во втором файле. Обработка почти наверняка будет медленнее, чем проверка сравнения.

Как для создания контрольной суммы: вы можете сделать это легко с помощью классов криптографии. Здесь короткий пример генерации контрольной суммы MD5 с С#.

Однако контрольная сумма может быть более быстрой и иметь больше смысла, если вы можете предварительно вычислить контрольную сумму "теста" или "базового" случая. Если у вас есть существующий файл, и вы проверяете, является ли новый файл тем же, что и существующий, предварительная вычисление контрольной суммы в вашем "существующем" файле означает, что нужно только один раз сделать DiskIO, на новый файл. Вероятно, это будет быстрее, чем сравнение побайтов.

Ответ 2

Самый медленный возможный метод - сравнивать два байта по байтам. Самое быстрое, что я смог придумать, - это аналогичное сравнение, но вместо одного байта за один раз вы должны использовать массив байтов размером с Int64, а затем сравнить полученные числа.

Вот что я придумал:

    const int BYTES_TO_READ = sizeof(Int64);

    static bool FilesAreEqual(FileInfo first, FileInfo second)
    {
        if (first.Length != second.Length)
            return false;

        if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase))
            return true;

        int iterations = (int)Math.Ceiling((double)first.Length / BYTES_TO_READ);

        using (FileStream fs1 = first.OpenRead())
        using (FileStream fs2 = second.OpenRead())
        {
            byte[] one = new byte[BYTES_TO_READ];
            byte[] two = new byte[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))
                    return false;
            }
        }

        return true;
    }

В моем тестировании я смог увидеть, что это превосходит простой сценарий ReadByte() почти на 3: 1. В среднем за 1000 прогонов я получил этот метод на уровне 1063 мс, а метод ниже (прямое байтовое сравнение по байтам) при 3031 мс. Хеширование всегда возвращалось на вторую секунду примерно в среднем 865 мс. Это тестирование имело видеофайл размером 100 МБ.

Здесь методы ReadByte и хэширования, которые я использовал, для сравнения:

    static bool FilesAreEqual_OneByte(FileInfo first, FileInfo second)
    {
        if (first.Length != second.Length)
            return false;

        if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase))
            return true;

        using (FileStream fs1 = first.OpenRead())
        using (FileStream fs2 = second.OpenRead())
        {
            for (int i = 0; i < first.Length; i++)
            {
                if (fs1.ReadByte() != fs2.ReadByte())
                    return false;
            }
        }

        return true;
    }

    static bool FilesAreEqual_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])
                return false;
        }
        return true;
    }

Ответ 3

Если вы решите, что вам действительно нужно полное побайтное сравнение (см. другие ответы для обсуждения хеширования), то решение в одну строку:

bool filesAreEqual = File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2));

В отличие от некоторых других опубликованных ответов, это работает правильно для любого типа файла: двоичного, текстового, мультимедийного, исполняемого и т.д., но в качестве полного двоичного сравнения, файлов, которые которые отличаются только "неважными" способами (такими как BOM, конец строки, кодировка символов, медиа-метаданные, пробелы, отступы, комментарии исходного кода, и т.д.) всегда будет считаться не равным.

Этот код полностью загружает оба файла в память, поэтому его не следует использовать для сравнения гигантских файлов. Помимо этого соображения, полная загрузка на самом деле не штраф; на самом деле, это может быть оптимальным решением .NET для размеров файлов, которые, как ожидается, будут меньше 85K, поскольку небольшие выделения в .NET очень дешевы, а приведенный выше код максимально делегирует производительность и оптимизацию файлов CLR/BCL.

Кроме того, для таких сценариев повседневной работы опасения по поводу производительности побайтового сравнения с помощью перечислителей LINQ (как показано здесь) являются спорными, поскольку попадание диска a̲t̲ a̲l̲l̲ для файлового ввода-вывода будет затухать на несколько порядков величины преимущества различных альтернатив сравнения памяти. Например, даже несмотря на то, что SequenceEqual действительно дает нам "оптимизацию" отказа от при первом несовпадении, это вряд ли имеет значение после того, как уже извлечено содержимое файлов, каждое из которых полностью необходимо для подтверждения соответствия.

С другой стороны, приведенный выше код не включает в себя активное прерывание для файлов разного размера, что может обеспечить ощутимую (возможно, измеримую) разницу в производительности. Это реально, потому что, хотя длина файла доступна в структуре WIN32_FILE_ATTRIBUTE_DATA (которая в любом случае должна быть извлечена первой для любого доступа к файлу), продолжение доступа к содержимому файла требует совершенно другой выборки, которую потенциально можно избежать. Если вас это беспокоит, решение состоит из двух строк:

// slight optimization over the code shown above
bool filesAreEqual = new FileInfo(path1).Length == new FileInfo(path2).Length && 
       File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2));

Всякий раз, когда значения Length для двух файлов не совсем равны, это немедленно возвращает false, даже не открывая файлы, что включает обработку случая, когда один из файлов пуст, а другой нет, Чтобы улучшить это немного дальше, также возможно избежать вторичных выборок, когда оба файла имеют Length ноль.

В настоящее время улучшенный код не позволяет избежать доступа к вторичному диску для двух пустых файлов, поскольку наличие равных значений Length - это именно то, что влечет за собой тот факт, что общее сравнение не может быть фальсифицировано. Но так как два пустых файла никогда не будут разными, на самом деле вам также никогда не нужно проверять, чтобы узнать, что любые два пустых файла всегда идентичны. Это означает, что мы также можем обойти открытие файлов и просто вернуть true в особом случае, когда обе длины равны нулю (не показано).

Ответ 4

В дополнение к ответу Рида Копси:

  • В худшем случае два файла идентичны. В этом случае лучше сравнить файлы побайтно.

  • Если два файла не идентичны, вы можете немного ускорить их, обнаружив, что они не идентичны.

Например, если два файла имеют разную длину, вы знаете, что они не могут быть идентичными, и вам даже не нужно сравнивать их фактический контент.

Ответ 5

Единственное, что может сделать сравнение контрольной суммы немного быстрее, чем сравнение побайтов, заключается в том, что вы читаете один файл за раз, что несколько сокращает время поиска для головки диска. Это небольшое усиление может быть очень хорошо съедено добавленным временем вычисления хеша.

Кроме того, сравнение контрольных сумм конечно только имеет шанс быть быстрее, если файлы идентичны. Если это не так, сравнение по байтам закончится с первой разницей, что значительно ускорит ее.

Вы также должны учитывать, что сравнение хеш-кода говорит только о том, что очень вероятно, что файлы идентичны. Чтобы быть на 100% уверенным, вам нужно выполнить побайтовое сравнение.

Если хеш-код, например, 32 бита, вы примерно 99.99999998% уверены, что файлы идентичны, если хэш-коды совпадают. Это близко к 100%, но если вам действительно нужна 100% уверенность, это не он.

Ответ 6

Это становится еще быстрее, если вы не читаете в маленьких 8 байтовых кусках, но ставите цикл, читая большой кусок. Я сократил среднее время сравнения до 1/4.

    public static bool FilesContentsAreEqual(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;
    }

    private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2)
    {
        const int bufferSize = 1024 * sizeof(Int64);
        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;
                }
            }
        }
    }
}

Ответ 7

Изменить: Этот метод не будет работать для сравнения двоичных файлов!

В .NET 4.0 класс File имеет следующие два новых метода:

public static IEnumerable<string> ReadLines(string path)
public static IEnumerable<string> ReadLines(string path, Encoding encoding)

Что вы можете использовать:

bool same = File.ReadLines(path1).SequenceEqual(File.ReadLines(path2));

Ответ 8

Честно говоря, я думаю, вам нужно как можно больше обрезать дерево поиска.

Вещи, которые нужно проверить перед тем, как идти побайтно:

  • Являются ли размеры одинаковыми?
  • Последний байт в файле A отличается от файла B

Кроме того, чтение больших блоков за раз будет более эффективным, поскольку диски быстрее считывают последовательные байты. Переход по байтам приводит не только к гораздо большему количеству системных вызовов, но приводит к тому, что считывание главы традиционного жесткого диска требует поиска назад и вперед чаще, если оба файла находятся на одном диске.

Прочитайте chunk A и chunk B в буфер байта и сравните их (НЕ используйте Array.Equals, см. комментарии). Настройте размер блоков до тех пор, пока вы не нажмете то, что, по вашему мнению, является хорошим компромиссом между памятью и производительностью. Вы также можете выполнить многопоточное сравнение, но не многопоточность чтения диска.

Ответ 9

Мой ответ является производным от @lars, но исправляет ошибку при вызове Stream.Read. Я также добавляю некоторые быстрые проверки пути, которые были получены другими ответами, и подтверждение ввода. Короче говоря, это должен быть ответ:

using System;
using System.IO;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            var fi1 = new FileInfo(args[0]);
            var fi2 = new FileInfo(args[1]);
            Console.WriteLine(FilesContentsAreEqual(fi1, fi2));
        }

        public static bool FilesContentsAreEqual(FileInfo fileInfo1, FileInfo fileInfo2)
        {
            if (fileInfo1 == null)
            {
                throw new ArgumentNullException(nameof(fileInfo1));
            }

            if (fileInfo2 == null)
            {
                throw new ArgumentNullException(nameof(fileInfo2));
            }

            if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            if (fileInfo1.Length != fileInfo2.Length)
            {
                return false;
            }
            else
            {
                using (var file1 = fileInfo1.OpenRead())
                {
                    using (var file2 = fileInfo2.OpenRead())
                    {
                        return StreamsContentsAreEqual(file1, file2);
                    }
                }
            }
        }

        private static int ReadFullBuffer(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;
        }

        private static bool StreamsContentsAreEqual(Stream stream1, Stream stream2)
        {
            const int bufferSize = 1024 * sizeof(Int64);
            var buffer1 = new byte[bufferSize];
            var buffer2 = new byte[bufferSize];

            while (true)
            {
                int count1 = ReadFullBuffer(stream1, buffer1);
                int count2 = ReadFullBuffer(stream2, buffer2);

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

Или, если вы хотите быть супер-удивительным, вы можете использовать асинхронный вариант:

using System;
using System.IO;
using System.Threading.Tasks;

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            var fi1 = new FileInfo(args[0]);
            var fi2 = new FileInfo(args[1]);
            Console.WriteLine(FilesContentsAreEqualAsync(fi1, fi2).GetAwaiter().GetResult());
        }

        public static async Task<bool> FilesContentsAreEqualAsync(FileInfo fileInfo1, FileInfo fileInfo2)
        {
            if (fileInfo1 == null)
            {
                throw new ArgumentNullException(nameof(fileInfo1));
            }

            if (fileInfo2 == null)
            {
                throw new ArgumentNullException(nameof(fileInfo2));
            }

            if (string.Equals(fileInfo1.FullName, fileInfo2.FullName, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            if (fileInfo1.Length != fileInfo2.Length)
            {
                return false;
            }
            else
            {
                using (var file1 = fileInfo1.OpenRead())
                {
                    using (var file2 = fileInfo2.OpenRead())
                    {
                        return await StreamsContentsAreEqualAsync(file1, file2).ConfigureAwait(false);
                    }
                }
            }
        }

        private static async Task<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;
        }

        private static async Task<bool> StreamsContentsAreEqualAsync(Stream stream1, Stream stream2)
        {
            const int bufferSize = 1024 * sizeof(Int64);
            var buffer1 = new byte[bufferSize];
            var buffer2 = new byte[bufferSize];

            while (true)
            {
                int count1 = await ReadFullBufferAsync(stream1, buffer1).ConfigureAwait(false);
                int count2 = await ReadFullBufferAsync(stream2, buffer2).ConfigureAwait(false);

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

Ответ 10

Мои эксперименты показывают, что это определенно помогает вызывать Stream.ReadByte() меньше раз, но использование BitConverter для пакетных байтов не имеет большого значения для сравнения байтов в массиве байтов.

Итак, можно заменить этот цикл "Math.Ceiling and iterations" в комментарии выше самым простым:

            for (int i = 0; i < count1; i++)
            {
                if (buffer1[i] != buffer2[i])
                    return false;
            }

Я предполагаю, что это связано с тем, что BitConverter.ToInt64 должен выполнить небольшую работу (проверить аргументы, а затем выполнить смещение бит), прежде чем сравнивать, и в итоге получается такая же работа, как и сравнение 8 байтов в двух массивах.

Ответ 11

Если файлы не слишком большие, вы можете использовать:

public static byte[] ComputeFileHash(string fileName)
{
    using (var stream = File.OpenRead(fileName))
        return System.Security.Cryptography.MD5.Create().ComputeHash(stream);
}

Можно только сравнить хэши, если хеши полезны для хранения.

(Отредактировал код для чего-то более чистого.)

Ответ 12

Другим усовершенствованием для больших файлов с одинаковой длиной может быть не чтение файлов последовательно, а сравнение более или менее случайных блоков.

Вы можете использовать несколько потоков, начиная с разных позиций в файле и сравнивая либо вперед, либо назад.

Таким образом, вы можете обнаруживать изменения в середине/конце файла, быстрее, чем вы могли бы получить там, используя последовательный подход.

Ответ 13

Если вам нужно сравнить только два файла, я думаю, что самый быстрый способ был бы (в C, я не знаю, применим ли он к .NET)

  • откройте оба файла f1, f2
  • получить соответствующую длину файла l1, l2
  • если l1!= l2, файлы разные; остановка
  • mmap() оба файла
  • используйте memcmp() в файлах mmap() ed.

OTOH, если вам нужно найти, если есть дубликаты файлов в наборе из N файлов, то самый быстрый способ, без сомнения, использует хеш, чтобы избежать N-way побитовых сравнений.

Ответ 14

Что-то (надеюсь) разумно эффективно:

public class FileCompare
{
    public static bool FilesEqual(string fileName1, string fileName2)
    {
        return FilesEqual(new FileInfo(fileName1), new FileInfo(fileName2));
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="file1"></param>
    /// <param name="file2"></param>
    /// <param name="bufferSize">8kb seemed like a good default</param>
    /// <returns></returns>
    public static bool FilesEqual(FileInfo file1, FileInfo file2, int bufferSize = 8192)
    {
        if (!file1.Exists || !file2.Exists || file1.Length != file2.Length) return false;

        var buffer1 = new byte[bufferSize];
        var buffer2 = new byte[bufferSize];

        using (var stream1 = file1.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            using (var stream2 = file2.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
            {

                while (true)
                {
                    var bytesRead1 = stream1.Read(buffer1, 0, bufferSize);
                    var bytesRead2 = stream2.Read(buffer2, 0, bufferSize);

                    if (bytesRead1 != bytesRead2) return false;
                    if (bytesRead1 == 0) return true;
                    if (!ArraysEqual(buffer1, buffer2, bytesRead1)) return false;
                }
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="array1"></param>
    /// <param name="array2"></param>
    /// <param name="bytesToCompare"> 0 means compare entire arrays</param>
    /// <returns></returns>
    public static bool ArraysEqual(byte[] array1, byte[] array2, int bytesToCompare = 0)
    {
        if (array1.Length != array2.Length) return false;

        var length = (bytesToCompare == 0) ? array1.Length : bytesToCompare;
        var tailIdx = length - length % sizeof(Int64);

        //check in 8 byte chunks
        for (var i = 0; i < tailIdx; i += sizeof(Int64))
        {
            if (BitConverter.ToInt64(array1, i) != BitConverter.ToInt64(array2, i)) return false;
        }

        //check the remainder of the array, always shorter than 8 bytes
        for (var i = tailIdx; i < length; i++)
        {
            if (array1[i] != array2[i]) return false;
        }

        return true;
    }
}

Ответ 15

Вот некоторые служебные функции, которые позволяют определить, содержат ли два файла (или два потока) идентичные данные.

Я предоставил "быструю" версию, которая многопоточна, поскольку она сравнивает байтовые массивы (каждый буфер, заполненный от того, что читается в каждом файле) в разных потоках, используя "Задачи".

Как и ожидалось, он намного быстрее (примерно в 3 раза быстрее), но он потребляет больше CPU (потому что он многопоточен) и больше памяти (потому что для каждого потока сравнения нужны два байтовых массива).

    public static bool AreFilesIdenticalFast(string path1, string path2)
    {
        return AreFilesIdentical(path1, path2, AreStreamsIdenticalFast);
    }

    public static bool AreFilesIdentical(string path1, string path2)
    {
        return AreFilesIdentical(path1, path2, AreStreamsIdentical);
    }

    public static bool AreFilesIdentical(string path1, string path2, Func<Stream, Stream, bool> areStreamsIdentical)
    {
        if (path1 == null)
            throw new ArgumentNullException(nameof(path1));

        if (path2 == null)
            throw new ArgumentNullException(nameof(path2));

        if (areStreamsIdentical == null)
            throw new ArgumentNullException(nameof(path2));

        if (!File.Exists(path1) || !File.Exists(path2))
            return false;

        using (var thisFile = new FileStream(path1, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        {
            using (var valueFile = new FileStream(path2, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                if (valueFile.Length != thisFile.Length)
                    return false;

                if (!areStreamsIdentical(thisFile, valueFile))
                    return false;
            }
        }
        return true;
    }

    public static bool AreStreamsIdenticalFast(Stream stream1, Stream stream2)
    {
        if (stream1 == null)
            throw new ArgumentNullException(nameof(stream1));

        if (stream2 == null)
            throw new ArgumentNullException(nameof(stream2));

        const int bufsize = 80000; // 80000 is below LOH (85000)

        var tasks = new List<Task<bool>>();
        do
        {
            // consumes more memory (two buffers for each tasks)
            var buffer1 = new byte[bufsize];
            var buffer2 = new byte[bufsize];

            int read1 = stream1.Read(buffer1, 0, buffer1.Length);
            if (read1 == 0)
            {
                int read3 = stream2.Read(buffer2, 0, 1);
                if (read3 != 0) // not eof
                    return false;

                break;
            }

            // both stream read could return different counts
            int read2 = 0;
            do
            {
                int read3 = stream2.Read(buffer2, read2, read1 - read2);
                if (read3 == 0)
                    return false;

                read2 += read3;
            }
            while (read2 < read1);

            // consumes more cpu
            var task = Task.Run(() =>
            {
                return IsSame(buffer1, buffer2);
            });
            tasks.Add(task);
        }
        while (true);

        Task.WaitAll(tasks.ToArray());
        return !tasks.Any(t => !t.Result);
    }

    public static bool AreStreamsIdentical(Stream stream1, Stream stream2)
    {
        if (stream1 == null)
            throw new ArgumentNullException(nameof(stream1));

        if (stream2 == null)
            throw new ArgumentNullException(nameof(stream2));

        const int bufsize = 80000; // 80000 is below LOH (85000)
        var buffer1 = new byte[bufsize];
        var buffer2 = new byte[bufsize];

        var tasks = new List<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 counts
            int read2 = 0;
            do
            {
                int read3 = stream2.Read(buffer2, read2, read1 - read2);
                if (read3 == 0)
                    return false;

                read2 += read3;
            }
            while (read2 < read1);

            if (!IsSame(buffer1, buffer2))
                return false;
        }
        while (true);
    }

    public static bool IsSame(byte[] bytes1, byte[] bytes2)
    {
        if (bytes1 == null)
            throw new ArgumentNullException(nameof(bytes1));

        if (bytes2 == null)
            throw new ArgumentNullException(nameof(bytes2));

        if (bytes1.Length != bytes2.Length)
            return false;

        for (int i = 0; i < bytes1.Length; i++)
        {
            if (bytes1[i] != bytes2[i])
                return false;
        }
        return true;
    }

Ответ 16

Я думаю, что есть приложения, где "хеш" быстрее, чем сравнение байта по байту. Если вам нужно сравнить файл с другими или иметь миниатюру фотографии, которая может измениться. Это зависит от того, где и как оно используется.

private bool CompareFilesByte(string file1, string file2)
{
    using (var fs1 = new FileStream(file1, FileMode.Open))
    using (var fs2 = new FileStream(file2, FileMode.Open))
    {
        if (fs1.Length != fs2.Length) return false;
        int b1, b2;
        do
        {
            b1 = fs1.ReadByte();
            b2 = fs2.ReadByte();
            if (b1 != b2 || b1 < 0) return false;
        }
        while (b1 >= 0);
    }
    return true;
}

private string HashFile(string file)
{
    using (var fs = new FileStream(file, FileMode.Open))
    using (var reader = new BinaryReader(fs))
    {
        var hash = new SHA512CryptoServiceProvider();
        hash.ComputeHash(reader.ReadBytes((int)file.Length));
        return Convert.ToBase64String(hash.Hash);
    }
}

private bool CompareFilesWithHash(string file1, string file2)
{
    var str1 = HashFile(file1);
    var str2 = HashFile(file2);
    return str1 == str2;
}

Здесь вы можете узнать, что является самым быстрым.

var sw = new Stopwatch();
sw.Start();
var compare1 = CompareFilesWithHash(receiveLogPath, logPath);
sw.Stop();
Debug.WriteLine(string.Format("Compare using Hash {0}", sw.ElapsedTicks));
sw.Reset();
sw.Start();
var compare2 = CompareFilesByte(receiveLogPath, logPath);
sw.Stop();
Debug.WriteLine(string.Format("Compare byte-byte {0}", sw.ElapsedTicks));

По желанию мы можем сохранить хэш в базе данных.

Надеюсь, это поможет

Ответ 17

Еще один ответ, полученный из @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>
public static bool IsSameContent(string file1, string file2)
{
    if (file1 == file2)
        return true;

    FileInfo file1Info = new FileInfo(file1);
    FileInfo file2Info = new FileInfo(file2);

    if (!file1Info.Exists && !file2Info.Exists)
       return true;
    if (!file1Info.Exists && file2Info.Exists)
        return false;
    if (file1Info.Exists && !file2Info.Exists)
        return false;
    if (file1Info.Length != file2Info.Length)
        return false;

    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])
                return false;
        }
        return true;
    }
}

Ответ 18

Это я нашел, хорошо работает, сравнивая сначала длину без чтения данных, а затем сравнивая последовательность байтов чтения

private static bool IsFileIdentical(string a, string b)
{            
   if (new FileInfo(a).Length != new FileInfo(b).Length) return false;
   return (File.ReadAllBytes(a).SequenceEqual(File.ReadAllBytes(b)));
}