Jak szybko porównać 2 pliki using.NET?

Typowe podejścia zalecają odczyt pliku binarnego przez FileStream i porównanie go bajt po bajcie.

  • czy porównanie sum kontrolnych, takich jak CRC, byłoby szybsze?
  • czy są jakieś biblioteki. NET, które mogą wygenerować sumę kontrolną dla pliku?
Author: John Saunders, 2009-08-31

18 answers

Porównanie sumy kontrolnej będzie najprawdopodobniej wolniejsze niż porównanie bajt po bajcie.

Aby wygenerować sumę kontrolną, musisz załadować każdy bajt pliku i wykonać na nim przetwarzanie. Będziesz musiał to zrobić w drugim pliku. Przetwarzanie będzie prawie na pewno wolniejsze niż sprawdzenie porównania.

Jeśli chodzi o generowanie sumy kontrolnej: możesz to zrobić łatwo z klasami kryptografii. Oto krótki przykład generowania sumy kontrolnej MD5 z C#.

Jednak suma kontrolna może być szybsza i mieć większy sens, jeśli można wstępnie obliczyć sumę kontrolną przypadku "test" lub "base". Jeśli masz istniejący plik i sprawdzasz, czy nowy plik jest taki sam jak istniejący, wstępne obliczenie sumy kontrolnej na "istniejącym" pliku oznaczałoby tylko konieczność wykonania DiskIO jeden raz, na nowym pliku. Byłoby to prawdopodobnie szybsze niż porównanie bajtów po bajtach.

 98
Author: Reed Copsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:26

Najwolniejszą możliwą metodą jest porównanie dwóch plików bajt po bajcie. Najszybsze, jakie udało mi się wymyślić, to podobne porównanie, ale zamiast jednego bajtu na raz, użyjesz tablicy bajtów o rozmiarze do Int64, a następnie porównasz liczby wynikowe.

Oto co wymyśliłem:

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

W moich testach, byłem w stanie zobaczyć, że to przewyższa prosty scenariusz ReadByte() o prawie 3:1. Średnio ponad 1000 uruchomień, mam tę metodę na 1063ms, a metoda poniżej (proste porównanie bajtów po bajtach) przy 3031ms. hashowanie zawsze wracało w subsekundach przy średnio 865ms. ten test był z ~ 100MB pliku wideo.

Oto metody ReadByte i hashujące, których użyłem dla celów porównawczych:

    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;
    }
 108
Author: chsh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-21 19:49:50

Oprócz Reed Copsey's odpowiedź:

  • W najgorszym przypadku te dwa pliki są identyczne. W takim przypadku najlepiej jest porównać pliki bajt po bajcie.

  • Jeśli oba pliki nie są identyczne, możesz przyspieszyć działanie, wykrywając wcześniej, że nie są identyczne.

Na przykład, jeśli dwa pliki są różnej długości, to wiesz, że nie mogą być identyczne, a nawet nie musisz porównywać ich rzeczywistego treść.

 31
Author: dtb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-31 18:02:33

Jeśli zdecydujesz, że naprawdę potrzebujesz pełnego porównania bajt-bajt (zobacz inne odpowiedzi do omówienia hashowania), to jednolinijkowe rozwiązanie to:

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

W przeciwieństwie do innych opublikowanych odpowiedzi, działa to poprawnie dla każdego rodzaju pliku: binarnego, tekstowego, multimedialnego, wykonywalnego itp., ale jako pełne binarne porównanie, pliki, które różnią się tylko W "nieistotny" sposób (np. BOM, zakończenie linii , kodowanie znaków , metadane multimediów, spacje, wypełnienia, komentarze do kodu źródłowego itp.) zawsze będą uważane za nie równe.

Ten kod ładuje oba pliki do pamięci całkowicie, więc nie powinien być używany do porównywania gigantycznych plików. Poza tym, pełne ładowanie nie jest tak naprawdę karą; w rzeczywistości może to być optymalne rozwiązanie. NET dla rozmiarów plików, które mają być mniejsze niż 85K, ponieważ małe alokacje w .NET są bardzo tanio i maksymalnie delegujemy wydajność i optymalizację plików do CLR/BCL.

Ponadto, w przypadku takich scenariuszy pracy, obawy dotyczące wydajności porównywania bajtów po bajtach za pomocą enumeratorów LINQ (Jak pokazano tutaj) są dyskusyjne, ponieważ uderzenie w dysk dla We/Wy pliku będzie przyćmić o kilka rzędów wielkości korzyści płynące z różnych alternatyw porównujących pamięć. Na przykład, mimo że SequenceEqual Czy faktycznie daje nam " optymalizację "porzucenie na pierwsza niezgodność , nie ma to znaczenia po pobraniu zawartości plików, z których każdy jest w pełni niezbędny do potwierdzenia dopasowania..

Z drugiej strony, powyższy kod nie zawiera eager abort dla plików różnej wielkości , które mogą zapewnić namacalną (być może mierzalną) różnicę wydajności. Ta jest namacalna, ponieważ podczas gdy długość pliku jest dostępna w strukturze WIN32_FILE_ATTRIBUTE_DATA (która i tak musi być pobrana jako pierwsza dla każdego dostępu do pliku), kontynuowanie dostępu do zawartości pliku wymaga zupełnie innego pobierania, którego potencjalnie można uniknąć. Jeśli się o to martwisz, rozwiązanie staje się dwiema liniami:

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

Można również rozszerzyć to, aby uniknąć wtórnych pobrań, jeśli (równoważne) Length wartości są równe zeru (Nie pokazane) i/lub aby uniknąć dwukrotnego budowania FileInfo (również nie pokazane).

 27
Author: Glenn Slayden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-05 19:45:48

Jedyną rzeczą, która może sprawić, że porównanie sumy kontrolnej będzie nieco szybsze niż porównanie bajt po bajcie, jest fakt, że czytasz jeden plik na raz, co nieco skraca czas wyszukiwania dla głowicy dysku. Ten niewielki przyrost może jednak bardzo dobrze zostać zjedzony przez dodany czas obliczania hash.

Ponadto porównanie sum kontrolnych oczywiście ma szansę być szybsze tylko wtedy, gdy pliki są identyczne. Jeśli tak nie jest, porównanie bajt-by-bajt zakończy się przy pierwszej różnicy, dzięki temu będzie o wiele szybciej.

Należy również wziąć pod uwagę, że porównanie kodu hashowego mówi tylko, że jest bardzo prawdopodobne, że pliki są identyczne. Aby mieć 100% pewności, musisz wykonać porównanie bajt po bajcie.

Jeśli na przykład kod skrótu wynosi 32 bity, masz około 99,99999998% pewności, że pliki są identyczne, jeśli kody skrótu pasują. To jest blisko 100%, ale jeśli naprawdę potrzebujesz 100% pewności, to nie to.

 15
Author: Guffa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-29 15:55:13

Robi się jeszcze szybciej, jeśli nie czytasz w małych 8-bajtowych fragmentach, ale wprowadzasz pętlę wokół, czytając większy fragment. Zmniejszyłem średni czas porównania do 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;
                }
            }
        }
    }
}
 14
Author: Lars,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-18 15:30:10

Edit: ta metoda by nie działała do porównywania plików binarnych!

W. NET 4.0 klasa File ma następujące dwie nowe metody:

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

Co oznacza, że możesz użyć:

bool same = File.ReadLines(path1).SequenceEqual(File.ReadLines(path2));
 9
Author: Sam Harwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-31 17:45:31

Szczerze, myślę, że musisz przyciąć swoje drzewo wyszukiwania jak najwięcej.

Rzeczy do sprawdzenia przed przejściem bajt po bajcie:

  1. czy rozmiary są takie same?
  2. jest ostatnim bajtem w pliku A innym niż plik B

Ponadto odczyt dużych bloków na raz będzie bardziej wydajny, ponieważ Napędy szybciej odczytują sekwencyjne bajty. Przechodzenie bajt po bajcie powoduje nie tylko znacznie więcej wywołań systemowych, ale powoduje, że głowica Odczytu tradycyjnego dysku twardego szuka tam iz powrotem częściej, jeśli oba pliki znajdują się na tym samym dysku.

Odczytaj fragment A i fragment B do bufora bajtów i porównaj je (nie używaj tablicy.Równe, patrz komentarze). Dostroić rozmiar bloków, aż trafisz to, co czujesz jest dobry kompromis między pamięcią i wydajności. Możesz również wielowątkowe porównanie, ale nie wielowątkowe odczyty dysku.

 6
Author: RandomInsano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-08 13:18:18

Moje eksperymenty pokazują, że zdecydowanie pomaga wywoływać strumień.ReadByte() mniej razy, ale użycie Bitconvertera do spakowania bajtów nie robi dużej różnicy w porównaniu z porównywaniem bajtów w tablicy bajtów.

Więc można zastąpić to " Math.Sufit i iteracje " pętla w komentarzu powyżej z najprostszym:

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

Myślę, że ma to związek z faktem, że BitConverter.ToInt64 musi wykonać trochę pracy (sprawdzić argumenty, a następnie wykonać przesunięcie bitowe) przed porównaj i to kończy się taką samą ilością pracy jak porównaj 8 bajtów w dwóch tablicach.

 2
Author: romeok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-16 01:49:55

Jeśli pliki nie są zbyt duże, możesz użyć:

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

Porównywanie skrótów będzie możliwe tylko wtedy, gdy będą one przydatne do przechowywania.

(edytował kod do czegoś znacznie czystszego.)

 1
Author: Cecil Has a Name,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-31 17:46:40

Kolejną poprawą w przypadku dużych plików o identycznej długości, może być nie odczytywanie plików sekwencyjnie, ale raczej porównywanie mniej lub bardziej przypadkowych bloków.

Możesz używać wielu wątków, zaczynając od różnych pozycji w pliku i porównując albo do przodu, albo do tyłu.

W ten sposób możesz wykryć zmiany na środku/końcu pliku, szybciej niż byś tam dotarł przy użyciu sekwencyjnego podejścia.

 1
Author: Thomas Kjørnes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-14 12:49:45

Jeśli trzeba tylko porównać dwa pliki, to chyba najszybszy sposób byłby (w C, Nie wiem czy dotyczy. NET)

  1. Otwórz oba pliki f1, f2
  2. pobierz odpowiednią długość pliku l1, l2
  3. if l1 != l2 pliki są różne; stop
  4. mmap () oba pliki
  5. Użyj memcmp()na plikach mmap () ed

OTOH, jeśli musisz znaleźć duplikaty plików w zestawie N plików, to najszybszym sposobem jest bez wątpienia użycie hasha, aby uniknąć N-way porównania bit po bitach.

 1
Author: CAFxX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-12 09:17:12

Coś (mam nadzieję) w miarę wydajnego:

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;
    }
}
 1
Author: Zar Shardan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-29 17:36:53

Oto kilka funkcji użytkowych, które pozwalają określić, czy dwa pliki (lub dwa strumienie) zawierają identyczne dane.

Podałem "szybką" wersję, która jest wielowątkowa, ponieważ porównuje tablice bajtów (każdy bufor wypełniony z tego, co zostało przeczytane w każdym pliku) w różnych wątkach za pomocą zadań.

Zgodnie z oczekiwaniami, jest znacznie szybszy (około 3x szybciej), ale zużywa więcej procesora (ponieważ jest wielowątkowy) i więcej pamięci (ponieważ potrzebuje dwóch buforów tablicy bajtów na porównanie wątek).

    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;
    }
 1
Author: Simon Mourier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-04 13:32:38

Moja odpowiedź jest pochodną @lars, ale naprawia błąd w wywołaniu do Stream.Read. Dodam również szybkie sprawdzanie ścieżek, które miały inne odpowiedzi, i walidację danych wejściowych. Krótko mówiąc, powinna to być odpowiedź:

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

Lub jeśli chcesz być super-awesome, możesz użyć wariantu asynchronicznego:

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;
                    }
                }
            }
        }
    }
}
 1
Author: Andrew Arnott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-21 20:31:58

Myślę, że są aplikacje, w których "hash" jest szybszy niż porównywanie bajtów po bajtach. Jeśli chcesz porównać Plik z innymi lub mieć miniaturę zdjęcia, które można zmienić. To zależy od tego, gdzie i jak jest używany.

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;
}
Tutaj możesz dostać to, co jest najszybsze.
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));

Opcjonalnie możemy zapisać hash w bazie danych.

Mam nadzieję, że to pomoże

 0
Author: antonio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-26 04:33:10

Kolejna odpowiedź, pochodzi z @ chsh. MD5 z uĹźyciem i skrăłtami dla tego samego pliku, Plik nie istnieje i róşne dĹ 'ugoĹ" ci:

/// <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;
    }
}
 0
Author: Andrew Taylor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-15 17:16:53

To, co znalazłem, działa dobrze porównując najpierw długość bez odczytu danych, a następnie porównując odczytaną sekwencję bajtów

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)));
}
 -1
Author: kernowcode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-25 09:15:55