.NET kullanarak 2 dosyayı hızlı bir şekilde nasıl karşılaştırabilirim?


Yanıtlar:


117

Bir sağlama toplamı karşılaştırması büyük olasılıkla bayt-bayt karşılaştırmasından daha yavaş olacaktır.

Bir sağlama toplamı oluşturmak için dosyanın her baytını yüklemeniz ve üzerinde işlem yapmanız gerekir. Daha sonra bunu ikinci dosyada yapmanız gerekir. İşlem neredeyse kesinlikle karşılaştırma kontrolünden daha yavaş olacaktır.

Bir sağlama toplamı oluşturmaya gelince: Bunu kriptografi sınıflarıyla kolayca yapabilirsiniz. İşte C # ile bir MD5 sağlama toplamı oluşturmanın kısa bir örneği .

Ancak, "test" veya "temel" durumunun sağlama toplamını önceden hesaplayabiliyorsanız, bir sağlama toplamı daha hızlı olabilir ve daha anlamlı olabilir. Varolan bir dosyanız varsa ve yeni bir dosyanın varolan dosyayla aynı olup olmadığını kontrol ediyorsanız, "varolan" dosyanızdaki sağlama toplamını önceden hesaplamak, DiskIO'nun yalnızca bir kez, yeni dosya. Bu muhtemelen bayt-byte-byte karşılaştırmasından daha hızlı olacaktır.


30
Dosyalarınızın bulunduğu yeri dikkate aldığınızdan emin olun. Yerel dosyaları dünyanın yarısında bir yedekle (veya korkunç bant genişliğine sahip bir ağ üzerinden) karşılaştırıyorsanız, ilk önce karma yapmak ve bayt akışı göndermek yerine ağ üzerinden bir sağlama toplamı göndermek daha iyi olabilir. karşılaştırmak.
Kim

@ReedCopsey: Benzer bir sorun yaşıyorum, çünkü çok sayıda kopya içerdiği düşünülen çeşitli ayrıntılarla üretilen girdi / çıktı dosyalarını saklamam gerekiyor. Önceden hesaplanmış karma kullanmayı düşündüm, ancak 2 (örneğin MD5) karma eşitse, 2 dosyanın eşit olduğunu ve daha fazla bayt-2 bayt karşılaştırmasından kaçınabileceğini makul bir şekilde varsayabilir miyim? Bildiğim kadarıyla MD5 / SHA1 vb çarpışmalar gerçekten olası ...
digEmAll

1
@digEmAll Çarpışma şansı düşük - her zaman daha güçlü bir karma yapabilirsiniz - yani: SHA1 yerine SHA256 kullanın, bu da çarpışma olasılığını daha da azaltacaktır.
Reed Copsey

Cevabınız için teşekkürler - sadece .net'e giriyorum. Ben bir hashcode / kontrol toplamı tekniği kullanıyorsa, o zaman ana klasörün hashes kalıcı bir yerde saklanacağını varsayıyorum? meraktan WPF uygulaması için nasıl saklarsınız - ne yapardınız? (şu anda xml, metin dosyaları veya veritabanlarına bakıyorum).
BKSpurgeon

139

Olası en yavaş yöntem iki dosya bayt bayt karşılaştırmasıdır. Gelebildiğim en hızlı benzer bir karşılaştırma, ancak bir seferde bir bayt yerine, Int64 boyutunda bir bayt dizisi kullanır ve sonuçta elde edilen sayıları karşılaştırırsınız.

İşte ben geldim:

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

Testlerimde, bu performansın neredeyse 3: 1 oranında basit bir ReadByte () senaryosundan daha iyi performans gösterdiğini gördüm. 1000'den fazla çalışmanın ortalaması alınarak, bu yöntemi 1063ms'de ve aşağıdaki yöntemi (basit bayt bayt karşılaştırması) 3031ms'de aldım. Hashing her zaman saniyenin altında ortalama 865 ms'de geri döndü. Bu test ~ 100MB'lık bir video dosyasıyla yapıldı.

Karşılaştırma amacıyla kullandığım ReadByte ve hashing yöntemleri:

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

1
Hayatımı kolaylaştırdın. Teşekkür ederim
anindis

2
@anindis: tamlığı için hem okumak isteyebilirsiniz @Lars' cevabını ve RandomInsano cevabı @ . Sevindim olsa yıllar boyunca yardımcı oldu! :)
chsh

1
FilesAreEqual_HashYöntem olmalıdır usinghem dosya üzerinde çok gibi akarsu ReadByteaksi takdirde her iki dosya asmak olacaktır yöntemle.
Ian Mercer

2
FileStream.Read()İstenen sayıdan daha az bayt okuyabileceğini unutmayın . Bunun StreamReader.ReadBlock()yerine kullanmalısınız .
Palec

2
Akış uzunluğu Int64'ün katı olmadığında Int64 sürümünde, son yineleme, önceki yinelemenin dolgusunu kullanarak doldurulmamış baytları karşılaştırır (bu da eşit olması gerekir). Ayrıca akış uzunluğu sizeof (Int64) değerinden küçükse, C # dizileri başlattığından doldurulmayan baytlar 0 olur. IMO, kod muhtemelen bu tuhaflıkları yorumlamak gerekir.
crokusek

46

Eğer varsa do gerçekten bir ihtiyaç karar tam bayt bayt karşılaştırma (hash tartışılması için diğer yanıtları görmek), sonra en kolay çözümdür:


System.IO.FileInfoörnekler için:

public static bool AreFileContentsEqual(FileInfo fi1, FileInfo fi2) =>
    fi1.Length == fi2.Length &&
    (fi1.Length == 0 || File.ReadAllBytes(fi1.FullName).SequenceEqual(
                        File.ReadAllBytes(fi2.FullName)));


System.Stringyol adları için:

public static bool AreFileContentsEqual(String path1, String path2) =>
                   AreFileContentsEqual(new FileInfo(path1), new FileInfo(path2));


Gönderilen diğer yanıtların aksine, bu her türlü dosya için kesin olarak doğrudur : ikili, metin, medya, yürütülebilir vb., Ancak tam bir ikili karşılaştırma olarak , yalnızca "önemsiz" şekillerde ( BOM , satır gibi) farklılık gösteren dosyalar -kullanma , karakter kodlama , medya meta verileri, boşluk, dolgu, kaynak kodu yorumları vb.) her zaman eşit değildir .

Bu kod her iki dosyayı da tamamen belleğe yükler, bu yüzden gerçekten devasa dosyaları karşılaştırmak için kullanılmamalıdır . Bu önemli uyarının ötesinde, .NET GC'nin tasarımı göz önüne alındığında tam yükleme gerçekten bir ceza değildir (çünkü küçük, kısa ömürlü tahsisleri son derece ucuz tutmak için temelde optimize edilmiştir ) ve aslında dosya boyutları beklendiğinde bile optimal olabilir daha az olacak şekilde 85K (burada gösterildiği gibi) kullanıcı kodunun minimum kullanarak maksimum dosya performans sorunları devrederek ima çünkü CLR, BCLve JIT(örneğin) son tasarım teknolojisi, sistem kodu ve adaptif çalışma zamanı optimizasyonlarından yararına.

Ayrıca, bu tür geçici senaryolar için, LINQnumaralandırıcılar (burada gösterildiği gibi) üzerinden bayt bayt karşılaştırması performansıyla ilgili endişeler tartışmalıdır, çünkü dosya G / Ç için diske a̲t̲ a̲l̲l̲ vurmak, çeşitli büyüklüklerde, çeşitli bellek karşılaştırma alternatifleri. Örneğin, aslında bize ilk uyumsuzluktan vazgeçmenin "optimizasyonunu" verse SequenceEqual de , dosya içeriğini zaten getirdikten sonra bu neredeyse hiç önemli değil, her biri eşleşmeyi onaylamak için gerekli ..


3
bu büyük dosyalar için iyi görünmüyor. bayt dizisini karşılaştırmaya başlamadan önce her iki dosyayı da sonuna kadar okuyacağından bellek kullanımı için iyi değildir. Bu yüzden tamponlu bir streamreader'ı tercih ederim.
Krypto_47

3
@ Krypto_47 Bu faktörleri ve cevabımın metninde uygun kullanımı tartıştım.
Glenn Slayden

33

Reed Copsey'nin cevabına ek olarak :

  • En kötü durum, iki dosyanın aynı olduğu durumdur. Bu durumda dosyaları byte-by-byte karşılaştırmak en iyisidir.

  • İki dosya aynı değilse, aynı olmadıklarını daha erken algılayarak işleri biraz hızlandırabilirsiniz.

Örneğin, iki dosya farklı uzunluktaysa, aynı olduklarını bilirsiniz ve gerçek içeriklerini karşılaştırmanız bile gerekmez.


10
Tam olarak: 1 pozisyondaki baytlar farklı olur olmaz diğer büyük kazanç durur.
Henk Holterman

6
@Henk: Bunun çok açık olduğunu düşündüm :-)
dtb

1
Bunu eklemede iyi bir nokta. Benim için açıktı, bu yüzden dahil etmedim, ama bahsetmek güzel.
Reed Copsey

16

Küçük 8 baytlık parçalar halinde okumazsanız, daha büyük bir yığın okuyarak bir döngü koyarsanız daha da hızlanır. Ortalama karşılaştırma süresini 1/4'e düşürdüm.

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

13
Genel olarak kontrol count1 != count2doğru değildir. Stream.Read()çeşitli nedenlerle sağladığınız sayıdan daha az geri dönebilir.
porges

1
Tampon çift sayıda yapacak emin olmak için Int64bloklar, şöyle boyutunu hesaplamak isteyebilirsiniz: const int bufferSize = 1024 * sizeof(Int64).
Jack A.

14

Bir sağlama toplamı karşılaştırmasını bayt-byte-byte karşılaştırmasından biraz daha hızlı hale getirebilecek tek şey, bir seferde bir dosya okumanız ve biraz da disk kafası için arama süresini azaltmanızdır. Bununla birlikte, bu küçük kazanç, hashın hesaplanması için eklenen süre ile çok iyi bir şekilde yenilebilir.

Ayrıca, bir sağlama toplamı karşılaştırmasının elbette dosyalar aynı ise daha hızlı olma şansı vardır. Değilse, bayt byte byte karşılaştırması ilk farkla sona erer ve çok daha hızlı hale gelir.

Ayrıca, bir karma kod karşılaştırmasının sadece bunun çok olası olduğunu söylediğini düşünmelisiniz. dosyaların özdeş olduğunu . % 100 emin olmak için bir byte-byte-by karşılaştırması yapmanız gerekir.

Örneğin, karma kodu 32 bit ise, karma kodları eşleşirse dosyaların aynı olduğundan yaklaşık% 99.99999998 emin olursunuz. Bu% 100'e yakın, ancak gerçekten% 100 kesinliğe ihtiyacınız varsa, bu değil.


Daha büyük bir karma kullanın ve testi yaparken bilgisayarın pozitif oranlarının çok altında bir yanlış pozitif olasılık elde edebilirsiniz.
Loren Pechtel

Karma zamanı vs arama zamanı hakkında katılmıyorum. Tek bir kafa araması sırasında birçok hesaplama yapabilirsiniz . Oranlar yüksek dosyaları eşleşen ben bit bir sürü ile bir karma kullanırdım. Bir eşleşme için makul bir şans varsa, bunları bir seferde bir blokla karşılaştırırım, örneğin 1MB bloklar için. (Kesinlikle sektörleri
bölmemenizi

1
@ Guffa'nın% 99.99999998 rakamını açıklamak 1 - (1 / (2^32))için, herhangi bir dosyanın 32 bitlik bir kareye sahip olma olasılığı olan hesaplamadan gelir . Aynı karma değerine sahip iki farklı dosyanın olasılığı aynıdır, çünkü ilk dosya "verilen" karma değerini sağlar ve yalnızca diğer dosyanın bu değerle eşleşip eşleşmediğini dikkate almamız gerekir. 64 ve 128 bit hashlama olasılığı% 99.999999999999999994 ve% 99.999999999999999999999999999999999999997 (sırasıyla) sanki bu gibi sayılamayacak kadar önemli.
Glenn Slayden

İnsanların neden ... Gerçekten de, bu rakamlar çoğu insan "Aynı karma koduna çarpışan sonsuz sayıda dosya" nin, gerçek de olsa, farazi olarak basit kavramı daha kavramak için zor olması açıklayabilir makul olmayan şüpheli karma-yönerge- kabul eşitlik.
Glenn Slayden

13

Düzenleme: Bu yöntem olurdu değil ikili dosyaları karşılaştırmak için çalışmak!

.NET 4.0'da, Filesınıfın aşağıdaki iki yeni yöntemi vardır:

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

Bu da kullanabileceğiniz anlamına gelir:

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

1
@dtb: İkili dosyalar için çalışmaz. Bunu fark ettiğimde ve yazımıma en üstteki düzenlemeyi eklediğimde muhtemelen yorum yazıyordunuz. : o
Sam Harwell

@ 280Z28: Ben bir şey demedim ;-)
dtb

Her iki dosyayı da bellekte saklamanız gerekmez mi?
RandomInsano

Dosya'nın, SequenceEquals kullanabilen ReadAllBytes işlevine sahip olduğunu ve bunun yerine tüm dosyalar üzerinde çalışacağını unutmayın. @RandomInsano'nun dediği gibi, bu bellekte saklanır, bu yüzden küçük dosyalar için kullanmak perferctly iyi olsa da büyük dosyalarla kullanmak dikkatli olurum.
DaedalusAlpha

1
@DaedalusAlpha Numaralandırılabilir bir değer döndürür, böylece hatlar isteğe bağlı olarak yüklenir ve sürekli olarak bellekte depolanmaz. Öte yandan ReadAllBytes, tüm dosyayı bir dizi olarak döndürür.
IllidanS4, Monica'nın

7

Dürüst olmak gerekirse, arama ağacınızı olabildiğince aşağıya indirmeniz gerektiğini düşünüyorum.

Byte-by-byte'a geçmeden önce kontrol edilmesi gerekenler:

  1. Boyutlar aynı mı?
  2. A dosyasındaki son bayt B dosyasından farklı mı

Ayrıca, disklerin sıralı baytları daha hızlı okuduğu için, aynı anda büyük blokları okumak daha verimli olacaktır. Bayt-by-byte'a gitmek sadece daha fazla sistem çağrısına neden olmakla kalmaz, aynı zamanda her iki dosya da aynı sürücüdeyse, geleneksel bir sabit diskin okuma kafasının daha sık aramasını sağlar.

A yığınını ve B yığınını bir bayt tamponu içinde okuyun ve karşılaştırın (Array.Equals kullanmayın, açıklamalara bakın). Bellek ve performans arasında iyi bir takas olduğunu düşündüğünüze ulaşıncaya kadar blokların boyutunu ayarlayın. Ayrıca karşılaştırmayı çok iş parçacığına da sahip olabilirsiniz, ancak diskin okuduğu çok iş parçacığını kullanamazsınız.


Array.Equals kullanmak kötü bir fikir çünkü bütün diziyi karşılaştırıyor. Muhtemelen, en az bir blok okunması tüm diziyi doldurmayacaktır.
Doug Clutter

Tüm diziyi karşılaştırmak neden kötü bir fikir? Bir blok okuması neden diziyi doldurmuyor? Kesinlikle iyi bir ayar noktası var, ama bu yüzden boyutlarla oynuyorsun. Ayrı bir iş parçacığında karşılaştırma yapmak için ekstra noktalar.
RandomInsano

Bir bayt dizisi tanımladığınızda, bunun sabit bir uzunluğu olacaktır. (örn. - var buffer = yeni bayt [4096]) Dosyadan bir blok okuduğunuzda, 4096 baytın tamamını döndürebilir veya döndürmeyebilir. Örneğin, dosya yalnızca 3000 bayt uzunluğundaysa.
Doug Clutter

Ah, şimdi anlıyorum! İyi haber, okuma diziye yüklenen bayt sayısını döndürecektir, bu nedenle dizi doldurulamazsa, veri olacaktır. Eşitliği test ettiğimiz için eski arabellek verileri önemli olmayacak. Dokümanlar: msdn.microsoft.com/en-us/library/9kstw824(v=vs.110).aspx
RandomInsano

Ayrıca, Equals () yöntemini kullanma önerim kötü bir fikir. Mono'da, öğeler bellekte bitişik olduğu için bir bellek karşılaştırması yaparlar. Ancak Microsoft bunu geçersiz kılmaz, bunun yerine yalnızca burada her zaman yanlış olacak bir referans karşılaştırması yapar.
RandomInsano

4

Cevabım @lars bir türevidir ancak çağrıdaki hatayı düzeltir Stream.Read. Ayrıca, diğer yanıtların sahip olduğu hızlı yol denetimi ve giriş doğrulaması da ekliyorum. Kısacası, bu olmalıdır cevap:

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

Veya süper harika olmak istiyorsanız, zaman uyumsuz varyantı kullanabilirsiniz:

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

bit dönüştürücü konvertör biti (var i = 0; i <sayım; i + = sizeof (uzun)) {if (BitConverter.ToInt64 (buffer1, i)! `` için daha iyi olmaz mı? = BitConverter.ToInt64 (buffer2, i)) {dönüş yanlış; }} ``
Simon

2

Deneylerim, Stream.ReadByte () işlevini daha az kez çağırmanın kesinlikle yardımcı olduğunu gösteriyor, ancak baytları paketlemek için BitConverter kullanmak bir bayt dizisindeki baytları karşılaştırmak konusunda fazla bir fark yaratmıyor.

Dolayısıyla, yukarıdaki açıklamadaki "Math.Ceiling ve iterations" döngüsünü en basit olanla değiştirmek mümkündür:

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

Ben karşılaştırmadan önce BitConverter.ToInt64 biraz iş yapmak gerekir (bağımsız değişkenleri kontrol ve daha sonra bit kaydırma gerçekleştirmek) gerçeği ile ilgili olduğunu tahmin ve iki dizide 8 bayt karşılaştırmak aynı miktarda iş biter .


1
Array.Equals sisteme daha derine iner, bu yüzden C #'da bayt baytına gitmekten çok daha hızlı olacaktır. Microsoft için konuşamıyorum, ancak derinlemesine, Mono dizi eşitliği için C'nin memcpy () komutunu kullanır. Bundan daha hızlı olamaz.
RandomInsano

2
@RandomInsano, memcpy () değil, memcmp () demek istediğinizi tahmin edin
SQL Police

1

Dosyalar çok büyük değilse şunları kullanabilirsiniz:

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

Karmaların yalnızca karmaların depolanması yararlı olduğu durumlarda karşılaştırılması mümkün olacaktır.

(Kodu daha temiz bir şeye düzenledi.)


1

Aynı uzunlukta büyük dosyalar üzerinde yapılan bir başka gelişme, dosyaları sırayla okumak değil, daha çok veya daha az rastgele blokları karşılaştırmak olabilir.

Dosyadaki farklı konumlardan başlayıp ileri veya geri karşılaştırarak birden fazla iş parçacığı kullanabilirsiniz.

Bu şekilde, dosyanın ortasında / sonunda değişiklikleri, sıralı bir yaklaşım kullanarak oraya ulaşacağınızdan daha hızlı algılayabilirsiniz.


1
Disk atma burada sorunlara neden olur mu?
RandomInsano

Fiziksel disk sürücüleri evet, SSD'ler bunu hallederdi.
TheLegendaryCopyCoder

1

Sadece iki dosyayı karşılaştırmanız gerekiyorsa, sanırım en hızlı yol (C'de .NET için geçerli olup olmadığını bilmiyorum)

  1. her iki dosyayı aç f1, f2
  2. ilgili dosya uzunluğunu al l1, l2
  3. l1! = l2 ise dosyalar farklıdır; Dur
  4. mmap () her iki dosya
  5. mmap () ed dosyalarında memcmp () kullanın

OTOH, bir dizi N dosyasında yinelenen dosyalar olup olmadığını bulmanız gerekiyorsa, N-yol bit-bit karşılaştırmaları önlemek için şüphesiz en hızlı yol bir karma kullanır.


1

(Umarım) makul derecede verimli bir şey:

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

İki dosyanın (veya iki akışın) aynı veri içerip içermediğini belirlemenizi sağlayan bazı yardımcı program işlevleri aşağıda verilmiştir.

Görevleri kullanarak farklı iş parçacıkları bayt dizileri (her dosyada okunan her tampon) karşılaştırır gibi çok iş parçacıklı bir "hızlı" sürümü sağladı.

Beklendiği gibi, çok daha hızlı (yaklaşık 3 kat daha hızlı), ancak daha fazla CPU (çok iş parçacıklı olduğu için) ve daha fazla bellek (çünkü karşılaştırma iş parçacığı başına iki bayt dizi arabelleği gerektirdiğinden) tüketiyor.

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

0

Bence "hash" byte byte byte karşılaştırmak daha hızlı uygulamalar vardır. Bir dosyayı başkalarıyla karşılaştırmanız veya değişebilecek bir fotoğrafın küçük resmine sahip olmanız gerekir. Nerede ve nasıl kullanıldığına bağlıdır.

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

Burada, en hızlı olanı alabilirsiniz.

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

İsteğe bağlı olarak, karmayı bir veritabanına kaydedebiliriz.

Umarım bu yardımcı olabilir


0

@Chsh'den türetilmiş başka bir cevap. Dosya ve kısayollar ile MD5 aynı dosya, dosya mevcut değil ve farklı uzunluklarda:

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

if (i>=secondHash.Length ...Hangi koşullar altında iki MD5 karmasının farklı uzunluklarda olacağını söylüyorsunuz ?
frogpelt

-1

Bu ben ilk önce veri okumadan uzunluğu karşılaştırmak ve daha sonra okuma bayt dizisi karşılaştırarak çalışır bulduk

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)));
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.