Bagaimana cara membandingkan 2 file dengan cepat menggunakan .NET?

Jawaban:

118

Perbandingan checksum kemungkinan besar akan lebih lambat daripada perbandingan byte-by-byte.

Untuk menghasilkan checksum, Anda harus memuat setiap byte file, dan melakukan pemrosesan di atasnya. Anda kemudian harus melakukan ini pada file kedua. Prosesnya hampir pasti akan lebih lambat daripada pemeriksaan perbandingan.

Adapun untuk menghasilkan checksum: Anda dapat melakukan ini dengan mudah dengan kelas kriptografi. Berikut adalah contoh singkat untuk menghasilkan checksum MD5 dengan C #.

Namun, checksum mungkin lebih cepat dan lebih masuk akal jika Anda dapat menghitung sebelumnya checksum untuk kasus "test" atau "base". Jika Anda memiliki file yang sudah ada, dan Anda memeriksa untuk melihat apakah file baru sama dengan yang sudah ada, pra-komputasi checksum pada file "yang ada" berarti hanya perlu melakukan DiskIO satu kali, di file baru. Ini kemungkinan akan lebih cepat daripada perbandingan byte-by-byte.

Reed Copsey
sumber
30
Pastikan untuk memperhitungkan di mana file Anda berada. Jika Anda membandingkan file lokal dengan cadangan setengah jalan di seluruh dunia (atau melalui jaringan dengan bandwidth yang mengerikan), Anda mungkin lebih baik melakukan hash terlebih dahulu dan mengirim checksum melalui jaringan daripada mengirim aliran byte ke membandingkan.
Kim
@ReedCopsey: Saya mengalami masalah yang sama, karena saya perlu menyimpan file input / output yang dihasilkan oleh beberapa elaborasi yang seharusnya berisi banyak duplikasi. Saya berpikir untuk menggunakan hash yang telah dihitung sebelumnya, tetapi menurut Anda apakah saya dapat berasumsi bahwa jika 2 (misalnya MD5) hash sama, 2 file sama dan menghindari perbandingan byte-2-byte lebih lanjut? Sejauh yang saya tahu tabrakan MD5 / SHA1 dll sangat tidak mungkin ...
digEmAll
1
@digEmAll Kemungkinan tabrakan rendah - Anda selalu dapat melakukan hash yang lebih kuat, meskipun - yaitu: gunakan SHA256 daripada SHA1, yang akan mengurangi kemungkinan tabrakan lebih lanjut.
Reed Copsey
terima kasih atas jawaban Anda - saya baru saja masuk ke .net. Saya berasumsi bahwa jika seseorang menggunakan teknik hashcode / check sum, maka hash dari folder utama akan disimpan terus-menerus di suatu tempat? karena penasaran bagaimana Anda menyimpannya untuk aplikasi WPF - apa yang akan Anda lakukan? (Saya sedang melihat xml, file teks atau database).
BKSpurgeon
139

Metode paling lambat yang mungkin adalah membandingkan dua file byte demi byte. Yang tercepat yang bisa saya dapatkan adalah perbandingan yang serupa, tetapi alih-alih satu byte pada satu waktu, Anda akan menggunakan array byte berukuran Int64, dan kemudian membandingkan angka yang dihasilkan.

Inilah yang saya dapatkan:

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

Dalam pengujian saya, saya dapat melihat ini mengungguli skenario ReadByte () langsung dengan hampir 3: 1. Rata-rata lebih dari 1000 berjalan, saya mendapatkan metode ini pada 1063ms, dan metode di bawah ini (perbandingan byte langsung dengan byte) pada 3031ms. Hashing selalu kembali sub-detik sekitar rata-rata 865ms. Pengujian ini dilakukan dengan file video ~ 100MB.

Inilah ReadByte dan metode hashing yang saya gunakan, untuk tujuan perbandingan:

    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;
    }
chsh
sumber
1
Anda membuat hidup saya lebih mudah. Terima kasih
anindis
2
@anindis: Untuk kelengkapan, Anda mungkin ingin membaca jawaban @Lars dan jawaban @ RandomInsano . Senang itu membantu selama bertahun-tahun! :)
chsh
1
The FilesAreEqual_HashMetode harus memiliki usingpada kedua berkas sungai juga seperti ReadBytemetode selain itu akan bertahan pada kedua file.
Ian Mercer
2
Perhatikan bahwa FileStream.Read()sebenarnya mungkin membaca byte kurang dari jumlah yang diminta. Anda harus menggunakan StreamReader.ReadBlock()sebagai gantinya.
Palec
2
Dalam versi Int64 ketika panjang streaming bukan kelipatan Int64 maka iterasi terakhir adalah membandingkan byte yang tidak terisi menggunakan isian iterasi sebelumnya (yang juga harus sama sehingga tidak masalah). Juga jika panjang aliran kurang dari sizeof (Int64) maka byte yang tidak terisi adalah 0 karena C # menginisialisasi array. IMO, kode mungkin harus mengomentari keanehan ini.
crokusek
52

Jika Anda tidak memutuskan Anda benar-benar membutuhkan perbandingan penuh byte-by-byte (lihat jawaban lainnya untuk diskusi dari hashing), maka solusi termudah adalah:


• untuk nama jalur `System.String`:
public static bool AreFileContentsEqual(String path1, String path2) =>
              File.ReadAllBytes(path1).SequenceEqual(File.ReadAllBytes(path2));

• untuk instance `System.IO.FileInfo`:
public static bool AreFileContentsEqual(FileInfo fi1, FileInfo fi2) =>
    fi1.Length == fi2.Length &&
    (fi1.Length == 0 || File.ReadAllBytes(fi1.FullName).SequenceEqual(
                        File.ReadAllBytes(fi2.FullName)));

Tidak seperti beberapa jawaban yang diposkan lainnya, ini secara meyakinkan benar untuk semua jenis file: biner, teks, media, dapat dieksekusi, dll., Tetapi sebagai perbandingan biner penuh , file yang berbeda hanya dengan cara yang "tidak penting" (seperti BOM , baris -ending , pengkodean karakter , metadata media, spasi, padding, komentar kode sumber, dll.) akan selalu dianggap tidak sama .

Kode ini memuat kedua file ke dalam memori seluruhnya, jadi tidak boleh digunakan untuk membandingkan file yang sangat besar . Di luar peringatan penting itu, pemuatan penuh sebenarnya bukan merupakan penalti mengingat desain .NET GC (karena secara fundamental dioptimalkan untuk menjaga alokasi kecil dan berumur pendek tetap sangat murah ), dan bahkan dapat menjadi optimal ketika ukuran file diharapkan kurang dari 85k , karena menggunakan minimal kode pengguna (seperti yang ditunjukkan di sini) menyiratkan maksimal mendelegasikan masalah kinerja file ke CLR, BCLdan JITuntuk manfaat dari (misalnya) teknologi terbaru desain, kode sistem, dan optimasi runtime adaptif.

Selain itu, untuk skenario hari kerja seperti itu, kekhawatiran tentang kinerja perbandingan byte-by-byte melalui LINQenumerator (seperti yang ditunjukkan di sini) diperdebatkan, karena menekan disk a̲t̲ a̲l̲l̲ untuk file I / O akan mengecil, dengan beberapa kali lipat, manfaatnya dari berbagai alternatif pembanding memori. Sebagai contoh, meskipun SequenceEqual tidak sebenarnya memberi kita "optimasi" dari meninggalkan pada ketidakcocokan pertama , ini hampir tidak penting setelah sudah diambil isinya file, masing-masing sepenuhnya diperlukan untuk mengkonfirmasi pertandingan ..

Glenn Slayden
sumber
3
yang satu ini tidak terlihat bagus untuk file besar. tidak baik untuk penggunaan memori karena akan membaca kedua file hingga akhir sebelum mulai membandingkan array byte. Itulah mengapa saya lebih suka menggunakan streamreader dengan buffer.
Krypto_47
3
@ Krypto_47 Saya membahas faktor-faktor ini dan penggunaan yang sesuai dalam teks jawaban saya.
Glenn Slayden
33

Selain jawaban Reed Copsey :

  • Kasus terburuk adalah di mana kedua file itu identik. Dalam hal ini yang terbaik adalah membandingkan file byte-by-byte.

  • Jika jika kedua file tidak identik, Anda dapat mempercepat sedikit dengan mendeteksi lebih cepat bahwa keduanya tidak identik.

Misalnya, jika kedua file memiliki panjang yang berbeda maka Anda tahu bahwa keduanya tidak dapat identik, dan Anda bahkan tidak perlu membandingkan konten sebenarnya.

dtb
sumber
10
Untuk menjadi lengkap: keuntungan besar lainnya berhenti segera setelah byte pada posisi 1 berbeda.
Henk Holterman
6
@Henk: Saya pikir ini terlalu jelas :-)
dtb
1
Poin bagus untuk menambahkan ini. Itu jelas bagi saya, jadi saya tidak memasukkannya, tapi bagus untuk disebutkan.
Reed Copsey
16

Ini menjadi lebih cepat jika Anda tidak membaca dalam potongan kecil 8 byte tetapi membuat putaran, membaca potongan yang lebih besar. Saya mengurangi waktu perbandingan rata-rata menjadi 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;
                }
            }
        }
    }
}
Lars
sumber
13
Secara umum pemeriksaan count1 != count2ini tidak benar. Stream.Read()dapat mengembalikan kurang dari hitungan yang Anda berikan, karena berbagai alasan.
Porges
1
Untuk memastikan bahwa buffer akan mengadakan jumlah yang lebih dari Int64blok, Anda mungkin ingin menghitung ukuran seperti ini: const int bufferSize = 1024 * sizeof(Int64).
Jack A.
14

Satu-satunya hal yang mungkin membuat perbandingan checksum sedikit lebih cepat daripada perbandingan byte-by-byte adalah kenyataan bahwa Anda membaca satu file pada satu waktu, agak mengurangi waktu pencarian untuk kepala disk. Keuntungan kecil itu bagaimanapun juga bisa dimakan oleh waktu tambahan untuk menghitung hash.

Selain itu, perbandingan checksum tentu saja hanya memiliki peluang lebih cepat jika filenya identik. Jika tidak, perbandingan byte-by-byte akan berakhir pada perbedaan pertama, membuatnya jauh lebih cepat.

Anda juga harus mempertimbangkan bahwa perbandingan kode hash hanya memberi tahu Anda bahwa kemungkinan besar file tersebut identik. Agar 100% yakin, Anda perlu melakukan perbandingan byte-by-byte.

Jika kode hash misalnya adalah 32 bit, Anda sekitar 99.99999998% yakin bahwa file tersebut identik jika kode hash cocok. Itu mendekati 100%, tetapi jika Anda benar-benar membutuhkan kepastian 100%, bukan itu.

Guffa
sumber
Gunakan hash yang lebih besar dan Anda bisa mendapatkan peluang positif palsu jauh di bawah peluang kesalahan komputer saat melakukan tes.
Loren Pechtel
Saya tidak setuju tentang waktu hash vs waktu pencarian. Anda dapat melakukan banyak kalkulasi selama satu pencarian kepala. Jika kemungkinannya tinggi bahwa file cocok, saya akan menggunakan hash dengan banyak bit. Jika ada kemungkinan kecocokan yang masuk akal, saya akan membandingkannya satu blok pada satu waktu, misalnya blok 1MB. (Pilih ukuran blok yang membagi 4k secara merata untuk memastikan Anda tidak pernah membagi sektor.)
Loren Pechtel
1
Untuk menjelaskan angka @ Guffa 99.99999998%, ini berasal dari komputasi 1 - (1 / (2^32)), yang merupakan probabilitas bahwa setiap file akan memiliki beberapa hash 32-bit. Probabilitas dua file berbeda memiliki hash yang sama adalah sama, karena file pertama memberikan nilai hash yang "diberikan", dan kita hanya perlu mempertimbangkan apakah file lain cocok dengan nilai itu atau tidak. Peluang dengan hashing 64- dan 128-bit menurun menjadi 99.999999999999999994% dan 99.99999999999999999999999999999999997% (masing-masing), seolah-olah itu penting dengan angka yang tidak terduga.
Glenn Slayden
... Memang, fakta bahwa angka-angka ini lebih sulit bagi kebanyakan orang untuk dipahami daripada gagasan sederhana yang diduga, meskipun benar, tentang "banyak file yang bertabrakan dengan kode hash yang sama" dapat menjelaskan mengapa manusia secara tidak wajar curiga menerima hash-as- persamaan.
Glenn Slayden
13

Sunting: Metode ini tidak akan berfungsi untuk membandingkan file biner!

Di .NET 4.0, Filekelas memiliki dua metode baru berikut ini:

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

Yang berarti Anda bisa menggunakan:

bool same = File.ReadLines(path1).SequenceEqual(File.ReadLines(path2));
Sam Harwell
sumber
1
@dtb: Ini tidak berfungsi untuk file biner. Anda mungkin sudah mengetik komentar ketika saya menyadarinya dan menambahkan edit di bagian atas posting saya. : o
Sam Harwell
@ 280Z28: Saya tidak mengatakan apa-apa ;-)
dtb
Bukankah Anda juga perlu menyimpan kedua file di memori?
RandomInsano
Perhatikan bahwa File juga memiliki fungsi ReadAllBytes yang dapat menggunakan SequenceEquals juga jadi gunakan itu karena akan berfungsi pada semua file. Dan seperti yang dikatakan @RandomInsano, ini disimpan dalam memori jadi meskipun tidak masalah jika digunakan untuk file kecil, saya akan berhati-hati menggunakannya dengan file besar.
DaedalusAlpha
1
@DaedalusAlpha Ia mengembalikan enumerable, sehingga baris akan dimuat sesuai permintaan dan tidak disimpan dalam memori sepanjang waktu. ReadAllBytes, di sisi lain, mengembalikan seluruh file sebagai array.
IllidanS4 mendukung Monica
7

Sejujurnya, saya pikir Anda perlu memangkas pohon pencarian Anda sebanyak mungkin.

Hal-hal yang perlu diperiksa sebelum beralih byte-by-byte:

  1. Apakah ukurannya sama?
  2. Apakah byte terakhir di file A berbeda dari file B

Selain itu, membaca blok besar pada satu waktu akan lebih efisien karena drive membaca byte berurutan lebih cepat. Menggunakan byte-by-byte tidak hanya menyebabkan lebih banyak panggilan sistem, tetapi juga menyebabkan kepala baca hard drive tradisional mencari bolak-balik lebih sering jika kedua file berada di drive yang sama.

Baca chunk A dan chunk B menjadi buffer byte, dan bandingkan keduanya (JANGAN gunakan Array.Equals, lihat komentar). Sesuaikan ukuran blok sampai Anda mencapai apa yang menurut Anda merupakan pertukaran yang baik antara memori dan kinerja. Anda juga dapat melakukan multi-utas perbandingan, tetapi jangan membuat multi-utas disk yang dibaca.

RandomInsano
sumber
Menggunakan Array.Equals adalah ide yang buruk karena membandingkan seluruh array. Sepertinya, setidaknya satu blok yang dibaca tidak akan memenuhi seluruh larik.
Doug Clutter
Mengapa membandingkan seluruh larik merupakan ide yang buruk? Mengapa blok baca tidak mengisi array? Pasti ada titik penyetelan yang bagus, tetapi itulah mengapa Anda bermain-main dengan ukurannya. Poin ekstra untuk melakukan perbandingan di utas terpisah.
RandomInsano
Ketika Anda mendefinisikan array byte, itu akan memiliki panjang tetap. (misalnya - var buffer = new byte [4096]) Ketika Anda membaca sebuah blok dari file, itu mungkin atau mungkin juga tidak mengembalikan 4096 byte penuh. Misalnya, jika panjang file hanya 3000 byte.
Doug Clutter
Ah, sekarang saya mengerti! Kabar baiknya adalah pembacaan akan mengembalikan jumlah byte yang dimuat ke dalam array, jadi jika array tidak dapat diisi, akan ada data. Karena kami menguji kesetaraan, data buffer lama tidak akan menjadi masalah. Docs: msdn.microsoft.com/en-us/library/9kstw824(v=vs.110).aspx
RandomInsano
Juga penting, rekomendasi saya untuk menggunakan metode Equals () adalah ide yang buruk. Di Mono, mereka melakukan perbandingan memori karena elemen-elemennya berdekatan dalam memori. Namun Microsoft tidak menimpanya, sebaliknya hanya melakukan perbandingan referensi yang di sini akan selalu salah.
RandomInsano
4

Jawaban saya adalah turunan dari @lars tetapi memperbaiki bug dalam panggilan ke Stream.Read. Saya juga menambahkan beberapa pemeriksaan jalur cepat yang dimiliki jawaban lain, dan validasi input. Singkatnya, ini harus yang jawabannya:

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

Atau jika Anda ingin menjadi super-hebat, Anda dapat menggunakan varian async:

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;
                    }
                }
            }
        }
    }
}
Andrew Arnott
sumber
Bukankah bit bitconverter akan lebih baik sebagai `` untuk (var i = 0; i <count; i + = sizeof (long)) {if (BitConverter.ToInt64 (buffer1, i)! = BitConverter.ToInt64 (buffer2, i)) {return false; }} ``
Simon
2

Eksperimen saya menunjukkan bahwa itu pasti membantu untuk memanggil Stream.ReadByte () lebih sedikit, tetapi menggunakan BitConverter untuk memaketkan byte tidak membuat banyak perbedaan dalam membandingkan byte dalam array byte.

Jadi dimungkinkan untuk mengganti loop "Math.Ceiling and iterations" dalam komentar di atas dengan yang paling sederhana:

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

Saya kira itu ada hubungannya dengan fakta bahwa BitConverter.ToInt64 perlu melakukan sedikit pekerjaan (periksa argumen dan kemudian lakukan pergeseran bit) sebelum Anda membandingkan dan itu akhirnya menjadi jumlah pekerjaan yang sama dengan membandingkan 8 byte dalam dua array .

romeok
sumber
1
Array.Equals masuk lebih dalam ke sistem, jadi kemungkinan akan jauh lebih cepat daripada pergi byte demi byte di C #. Saya tidak dapat berbicara untuk Microsoft, tetapi jauh di lubuk hati, Mono menggunakan perintah memcpy () C untuk kesetaraan array. Tidak bisa lebih cepat dari itu.
RandomInsano
2
@RandomInsano menebak maksudmu memcmp (), bukan memcpy ()
SQL Police
1

Jika file tidak terlalu besar, Anda dapat menggunakan:

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

Membandingkan hash hanya dapat dilakukan jika hash berguna untuk disimpan.

(Mengedit kode menjadi sesuatu yang jauh lebih bersih.)

Cecil Memiliki Nama
sumber
1

Perbaikan lain pada file besar dengan panjang yang identik, mungkin dengan tidak membaca file secara berurutan, tetapi membandingkan lebih banyak atau lebih sedikit blok acak.

Anda dapat menggunakan beberapa utas, mulai dari posisi berbeda dalam file dan membandingkan maju atau mundur.

Dengan cara ini Anda dapat mendeteksi perubahan di tengah / akhir file, lebih cepat daripada yang Anda lakukan di sana menggunakan pendekatan sekuensial.

Thomas Kjørnes
sumber
1
Apakah perontokan disk akan menyebabkan masalah di sini?
RandomInsano
Disk drive fisik ya, SSD akan menangani ini.
TheLegendaryCopyCoder
1

Jika Anda hanya perlu membandingkan dua file, saya kira cara tercepat adalah (di C, saya tidak tahu apakah itu berlaku untuk .NET)

  1. buka kedua file f1, f2
  2. dapatkan panjang file masing-masing l1, l2
  3. jika l1! = l2 file berbeda; berhenti
  4. mmap () kedua file
  5. gunakan memcmp () pada file mmap () ed

OTOH, jika Anda perlu menemukan apakah ada file duplikat dalam satu set N file, maka cara tercepat tidak diragukan lagi adalah menggunakan hash untuk menghindari perbandingan bit-by-bit N-way.

CAFxX
sumber
1

Sesuatu (semoga) cukup efisien:

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;
    }
}
Zar Shardan
sumber
1

Berikut adalah beberapa fungsi utilitas yang memungkinkan Anda menentukan apakah dua file (atau dua aliran) berisi data yang identik.

Saya telah menyediakan versi "cepat" yang multi-threaded karena membandingkan array byte (setiap buffer diisi dari apa yang telah dibaca di setiap file) di thread berbeda menggunakan Tasks.

Seperti yang diharapkan, ini jauh lebih cepat (sekitar 3x lebih cepat) tetapi mengkonsumsi lebih banyak CPU (karena multi threaded) dan lebih banyak memori (karena membutuhkan dua buffer array byte per thread perbandingan).

    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;
    }
Simon Mourier
sumber
0

Saya rasa ada aplikasi di mana "hash" lebih cepat daripada membandingkan byte demi byte. Jika Anda perlu membandingkan file dengan orang lain atau memiliki thumbnail foto yang bisa berubah. Itu tergantung di mana dan bagaimana itu digunakan.

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

Di sini, Anda bisa mendapatkan yang tercepat.

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

Secara opsional, kita dapat menyimpan hash dalam database.

Semoga ini bisa membantu

antonio
sumber
0

Ini saya telah menemukan bekerja dengan baik membandingkan pertama panjangnya tanpa membaca data dan kemudian membandingkan urutan byte yang dibaca

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)));
}
kernowcode.dll
sumber
0

Jawaban lain, berasal dari @chsh. MD5 dengan penggunaan dan shortcut untuk file yang sama, file tidak ada dan panjangnya berbeda:

/// <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;
    }
}
Andrew Taylor
sumber
Anda mengatakan if (i>=secondHash.Length ...Dalam keadaan apa dua hash MD5 memiliki panjang yang berbeda?
frogpelt