Làm thế nào để so sánh nhanh 2 tập tin bằng .NET?


Câu trả lời:


117

So sánh tổng kiểm tra rất có thể sẽ chậm hơn so với so sánh theo byte.

Để tạo tổng kiểm tra, bạn sẽ cần tải từng byte của tệp và thực hiện xử lý trên đó. Sau đó, bạn sẽ phải làm điều này trên tập tin thứ hai. Việc xử lý gần như chắc chắn sẽ chậm hơn so với kiểm tra so sánh.

Đối với việc tạo tổng kiểm tra: Bạn có thể thực hiện việc này một cách dễ dàng với các lớp mật mã. Đây là một ví dụ ngắn về việc tạo tổng kiểm MD5 với C #.

Tuy nhiên, tổng kiểm tra có thể nhanh hơn và có ý nghĩa hơn nếu bạn có thể tính toán trước tổng kiểm tra của trường hợp "kiểm tra" hoặc "cơ sở". Nếu bạn có một tệp hiện có và bạn đang kiểm tra xem một tệp mới có giống với tệp hiện có hay không, thì việc tính toán trước tổng kiểm tra trên tệp "hiện tại" của bạn chỉ có nghĩa là chỉ cần thực hiện DiskIO một lần, trên tập tin mới. Điều này có thể sẽ nhanh hơn so với so sánh theo byte.


30
Hãy chắc chắn để đưa vào tài khoản nơi tập tin của bạn được đặt. Nếu bạn đang so sánh các tệp cục bộ với một nửa dự phòng trên toàn thế giới (hoặc qua một mạng có băng thông khủng khiếp), bạn có thể tốt hơn nên băm trước và gửi một tổng kiểm tra qua mạng thay vì gửi một luồng byte tới đối chiếu.
Kim

@ReedCopsey: Tôi đang gặp vấn đề tương tự, vì tôi cần lưu trữ các tệp đầu vào / đầu ra được tạo ra bởi một số chi tiết được cho là có chứa nhiều bản sao. Tôi đã nghĩ sử dụng hàm băm được tính toán trước, nhưng bạn có nghĩ rằng tôi có thể giả sử một cách hợp lý rằng nếu băm 2 (ví dụ MD5) bằng nhau thì 2 tệp bằng nhau và tránh so sánh byte-2 byte hơn nữa không? Theo như tôi biết thì va chạm MD5 / SHA1 v.v ... thực sự rất khó xảy ra ...
digEmAll

1
@digEmAll Cơ hội va chạm là thấp - bạn luôn có thể thực hiện băm mạnh hơn - ví dụ: sử dụng SHA256 thay vì SHA1, điều này sẽ làm giảm khả năng va chạm hơn nữa.
Sậy Copsey

cảm ơn câu trả lời của bạn - tôi mới vào được .net. Tôi giả sử rằng nếu một người đang sử dụng kỹ thuật băm / kiểm tra tổng, thì băm của thư mục chính sẽ được lưu trữ liên tục ở đâu đó? Vì tò mò làm thế nào bạn sẽ lưu trữ nó cho một ứng dụng WPF - bạn sẽ làm gì? (Tôi hiện đang xem xml, tệp văn bản hoặc cơ sở dữ liệu).
BKSpurgeon

139

Phương pháp chậm nhất có thể là so sánh hai tệp byte theo byte. Cách nhanh nhất tôi có thể đưa ra là so sánh tương tự, nhưng thay vì một byte mỗi lần, bạn sẽ sử dụng một mảng byte có kích thước bằng Int64, sau đó so sánh các số kết quả.

Đây là những gì tôi nghĩ ra:

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

Trong thử nghiệm của mình, tôi đã có thể thấy điều này vượt trội hơn so với kịch bản ReadByte () đơn giản gần 3: 1. Tính trung bình trên 1000 lần chạy, tôi có phương thức này ở tốc độ 1063ms và phương thức bên dưới (so sánh đơn giản bằng cách so sánh byte) ở 3031ms. Băm luôn trở lại phụ thứ hai ở mức trung bình 865ms. Thử nghiệm này là với một tệp video ~ 100 MB.

Đây là phương pháp ReadByte và băm tôi đã sử dụng, để so sánh:

    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
Bạn làm cho cuộc sống của tôi dễ dàng hơn. Cảm ơn bạn
anindis

2
@anindis: Để đầy đủ, bạn có thể muốn đọc cả câu trả lời của @Larscâu trả lời của @ RandomInsano . Vui vì nó đã giúp rất nhiều năm mặc dù! :)
chsh

1
Các FilesAreEqual_Hashphương pháp nên có một usingtrên cả hai tập tin suối quá như ReadBytephương pháp nếu không nó sẽ treo trên cho cả hai tập tin.
Ian Mercer

2
Lưu ý rằng FileStream.Read()thực sự có thể đọc ít byte hơn số lượng được yêu cầu. Bạn nên sử dụng StreamReader.ReadBlock()thay thế.
Palec

2
Trong phiên bản Int64 khi độ dài luồng không phải là bội số của Int64 thì lần lặp cuối cùng là so sánh các byte không được sử dụng bằng cách điền vào lần lặp trước đó (cũng nên bằng nhau vì vậy nó vẫn ổn). Ngoài ra nếu độ dài luồng nhỏ hơn sizeof (Int64) thì các byte không được lấp đầy là 0 do C # khởi tạo mảng. IMO, mã có lẽ nên bình luận những điều kỳ lạ.
crokusek

46

Nếu bạn làm quyết định bạn thực sự cần một byte-by-byte so sánh đầy đủ (xem câu trả lời khác để thảo luận các băm), sau đó là giải pháp đơn giản nhất là:


System.IO.FileInfoví dụ:

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


• cho System.Stringtên đường dẫn:

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


Không giống như một số câu trả lời được đăng khác, điều này hoàn toàn chính xác cho bất kỳ loại tệp nào: nhị phân, văn bản, phương tiện, thực thi, v.v., nhưng như một so sánh nhị phân đầy đủ , các tệp chỉ khác nhau theo cách "không quan trọng" (như BOM , dòng -ending , mã hóa ký tự , siêu dữ liệu phương tiện, khoảng trắng, phần đệm, nhận xét mã nguồn, v.v.) sẽ luôn được coi là không bằng nhau .

Mã này tải cả hai tệp vào bộ nhớ hoàn toàn, vì vậy không nên sử dụng nó để so sánh các tệp thực sự khổng lồ . Ngoài sự cảnh báo quan trọng đó, tải đầy đủ thực sự không phải là một hình phạt đối với thiết kế của .NET GC (vì về cơ bản nó được tối ưu hóa để giữ các phân bổ nhỏ, ngắn hạn cực kỳ rẻ ), và trên thực tế thậm chí có thể tối ưu khi kích thước tệp được mong đợi được ít hơn 85k , vì sử dụng tối thiểu là mã người dùng (như trình bày ở đây) ngụ ý tối đa ủy thác vấn đề hiệu suất tập tin vào CLR, BCLJITđể được hưởng lợi từ (ví dụ) công nghệ mới nhất thiết kế, mã hệ thống, và tối ưu hóa thời gian chạy thích nghi.

Hơn nữa, đối với các kịch bản công việc như vậy, mối quan tâm về hiệu suất so sánh từng byte thông qua LINQcác điều tra viên (như được hiển thị ở đây) là không cần thiết, vì việc nhấn vào đĩa a̲t̲ a̲l̲l̲ cho tệp I / O sẽ giảm đi, theo một số bậc độ lớn, lợi ích của các lựa chọn so sánh bộ nhớ khác nhau. Ví dụ, mặc dù SequenceEqual không thực tế cho chúng ta những "tối ưu hóa" của bỏ trên không phù hợp đầu tiên , điều này hầu như không quan trọng sau khi đã lấy nội dung của các tác phẩm, mỗi hoàn toàn cần thiết để khẳng định trận đấu ..


3
cái này không tốt cho các tập tin lớn không tốt cho việc sử dụng bộ nhớ vì nó sẽ đọc cả hai tệp cho đến hết trước khi bắt đầu so sánh mảng byte. Đó là lý do tại sao tôi thà đi tìm một bộ đọc luồng với bộ đệm.
Krypto_47

3
@ Krypto_47 Tôi đã thảo luận về các yếu tố này và việc sử dụng chính xác trong văn bản câu trả lời của tôi.
Glenn Slayden

33

Ngoài câu trả lời của Reed Copsey :

  • Trường hợp xấu nhất là hai tệp giống hệt nhau. Trong trường hợp này, tốt nhất là so sánh các tệp theo từng byte.

  • Nếu hai tệp không giống nhau, bạn có thể tăng tốc mọi thứ lên một chút bằng cách phát hiện sớm hơn rằng chúng không giống nhau.

Ví dụ: nếu hai tệp có độ dài khác nhau thì bạn biết chúng không thể giống nhau và thậm chí bạn không phải so sánh nội dung thực tế của chúng.


10
Để được hoàn thành: mức tăng lớn khác đang dừng ngay khi các byte ở vị trí 1 khác nhau.
Henk Holterman

6
@Henk: Tôi nghĩ điều này quá rõ ràng :-)
dtb

1
Điểm tốt về việc thêm này. Đó là điều hiển nhiên đối với tôi, vì vậy tôi đã không bao gồm nó, nhưng thật tốt khi đề cập đến.
Sậy Copsey

16

Nó thậm chí còn nhanh hơn nếu bạn không đọc trong các đoạn nhỏ 8 byte mà đặt một vòng lặp, đọc một đoạn lớn hơn. Tôi giảm thời gian so sánh trung bình xuống còn 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;
                }
            }
        }
    }
}

13
Nói chung, kiểm tra count1 != count2không chính xác. Stream.Read()có thể trả lại ít hơn số lượng bạn đã cung cấp, vì nhiều lý do.
porges

1
Để đảm bảo bộ đệm sẽ giữ số Int64khối chẵn , bạn có thể muốn tính kích thước như thế này : const int bufferSize = 1024 * sizeof(Int64).
Jack A.

14

Điều duy nhất có thể làm cho một so sánh tổng kiểm tra nhanh hơn một chút so với so sánh từng byte là thực tế là bạn đang đọc một tệp tại một thời điểm, phần nào giảm thời gian tìm kiếm cho đầu đĩa. Tuy nhiên, mức tăng nhẹ đó có thể bị ăn mòn bởi thời gian tính toán băm.

Ngoài ra, một so sánh tổng kiểm tra tất nhiên chỉ có bất kỳ cơ hội nhanh hơn nếu các tập tin là giống hệt nhau. Nếu chúng không như vậy, một so sánh từng byte sẽ kết thúc ở sự khác biệt đầu tiên, làm cho nó nhanh hơn rất nhiều.

Bạn cũng nên xem xét rằng so sánh mã băm chỉ cho bạn biết rằng rất có khả năng các tệp giống hệt nhau. Để chắc chắn 100%, bạn cần thực hiện so sánh theo từng byte.

Ví dụ, nếu mã băm là 32 bit, bạn chắc chắn khoảng 99.99999998% rằng các tệp giống hệt nhau nếu mã băm khớp. Đó là gần 100%, nhưng nếu bạn thực sự cần 100% chắc chắn, thì không phải vậy.


Sử dụng hàm băm lớn hơn và bạn có thể nhận được tỷ lệ sai của dương tính giả thấp hơn tỷ lệ cược máy tính bị lỗi trong khi thực hiện kiểm tra.
Loren Pechtel

Tôi không đồng ý về thời gian băm so với thời gian tìm kiếm. Bạn có thể thực hiện rất nhiều tính toán trong một lần tìm kiếm đầu. Nếu tỷ lệ cược cao mà các tệp khớp nhau, tôi sẽ sử dụng hàm băm với rất nhiều bit. Nếu có cơ hội hợp lý của trận đấu, tôi sẽ so sánh chúng một khối tại một thời điểm, ví dụ như các khối 1MB. (Chọn kích thước khối mà 4k chia đều để đảm bảo bạn không bao giờ chia các thành phần.)
Loren Pechtel

1
Để giải thích con số 99.99999998% của @ Guffa, nó xuất phát từ điện toán 1 - (1 / (2^32)), đó là xác suất mà bất kỳ tệp nào sẽ có một số băm 32 bit nhất định. Xác suất hai tệp khác nhau có cùng hàm băm là như nhau, vì tệp đầu tiên cung cấp giá trị băm "đã cho" và chúng ta chỉ cần xem xét liệu tệp kia có khớp với giá trị đó hay không. Cơ hội với băm 64 và 128 bit giảm xuống 99.999999999999999994% và 99.9999999999999999999999999999999999997% (tương ứng), như thể đó là vấn đề với những con số không thể tưởng tượng được.
Glenn Slayden 19/03/2016

... Thật vậy, thực tế là những con số này khó nắm bắt hơn so với khái niệm đơn giản chính thức, mặc dù đúng, "vô số tập tin va chạm vào cùng một mã băm" có thể giải thích lý do tại sao con người nghi ngờ chấp nhận hàm băm một cách vô lý bình đẳng.
Glenn Slayden

13

Chỉnh sửa: Phương pháp này sẽ không hoạt động để so sánh các tệp nhị phân!

Trong .NET 4.0, Filelớp có hai phương thức mới sau đây:

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

Có nghĩa là bạn có thể sử dụng:

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

1
@dtb: Nó không hoạt động cho các tệp nhị phân. Có lẽ bạn đã gõ bình luận khi tôi nhận ra điều đó và thêm phần chỉnh sửa ở đầu bài viết của tôi. : o
Sam Harwell

@ 280Z28: Tôi không nói gì cả ;-)
dtb

Bạn cũng không cần lưu trữ cả hai tập tin trong bộ nhớ chứ?
RandomInsano

Lưu ý rằng Tệp cũng có chức năng ReadAllBytes, có thể sử dụng SequenceEquals, vì vậy hãy sử dụng nó thay vì nó sẽ hoạt động trên tất cả các tệp. Và như @RandomInsano đã nói, cái này được lưu trữ trong bộ nhớ nên trong khi sử dụng tốt cho các tệp nhỏ, tôi sẽ cẩn thận khi sử dụng nó với các tệp lớn.
DaedalusAlpha

1
@DaedalusAlpha Nó trả về vô số, vì vậy các dòng sẽ được tải theo yêu cầu và không được lưu trữ trong bộ nhớ trong toàn bộ thời gian. Mặt khác, ReadAllBytes không trả về toàn bộ tệp dưới dạng một mảng.
IllidanS4 muốn Monica trở lại vào

7

Thành thật mà nói, tôi nghĩ bạn cần cắt tỉa cây tìm kiếm của bạn xuống càng nhiều càng tốt.

Những điều cần kiểm tra trước khi đi từng byte:

  1. Có kích thước giống nhau không?
  2. Là byte cuối cùng trong tệp A khác với tệp B

Ngoài ra, đọc các khối lớn tại một thời điểm sẽ hiệu quả hơn vì các ổ đĩa đọc các byte tuần tự nhanh hơn. Việc đi từng byte một không chỉ gây ra nhiều cuộc gọi hệ thống hơn mà còn khiến đầu đọc của ổ cứng truyền thống tìm kiếm qua lại thường xuyên hơn nếu cả hai tệp nằm trên cùng một ổ đĩa.

Đọc đoạn A và đoạn B vào bộ đệm byte và so sánh chúng (KHÔNG sử dụng Array.Equals, xem bình luận). Điều chỉnh kích thước của các khối cho đến khi bạn đạt được những gì bạn cảm thấy là một sự đánh đổi tốt giữa bộ nhớ và hiệu suất. Bạn cũng có thể đa luồng so sánh, nhưng không đa luồng mà đĩa đọc được.


Sử dụng Array.Equals là một ý tưởng tồi vì nó so sánh toàn bộ mảng. Có khả năng, ít nhất một khối đọc sẽ không lấp đầy toàn bộ mảng.
Doug Clutter

Tại sao so sánh toàn bộ mảng là một ý tưởng tồi? Tại sao một khối đọc không điền vào mảng? Chắc chắn có một điểm điều chỉnh tốt, nhưng đó là lý do tại sao bạn chơi với kích thước. Thêm điểm để làm so sánh trong một chủ đề riêng biệt.
RandomInsano

Khi bạn xác định một mảng byte, nó sẽ có độ dài cố định. (ví dụ: var buffer = new byte [4096]) Khi bạn đọc một khối từ tệp, nó có thể hoặc không thể trả về 4096 byte đầy đủ. Chẳng hạn, nếu tệp chỉ dài 3000 byte.
Doug Clutter

À, giờ thì tôi đã hiểu! Tin tốt là việc đọc sẽ trả về số byte được tải vào mảng, vì vậy nếu mảng không thể được điền, sẽ có dữ liệu. Vì chúng tôi đang kiểm tra sự bình đẳng, dữ liệu bộ đệm cũ sẽ không thành vấn đề. Tài liệu: msdn.microsoft.com/en-us/l
Library / 9kstw824 (v = vs.110) .aspx

Cũng quan trọng, khuyến nghị của tôi để sử dụng phương thức Equals () là một ý tưởng tồi. Trong Mono, họ thực hiện so sánh bộ nhớ vì các yếu tố tiếp giáp nhau trong bộ nhớ. Tuy nhiên, Microsoft không ghi đè lên nó, thay vào đó chỉ thực hiện một so sánh tham chiếu mà ở đây luôn luôn là sai.
RandomInsano

4

Câu trả lời của tôi là một dẫn xuất của @lars nhưng sửa lỗi trong lệnh gọi đến Stream.Read. Tôi cũng thêm một số kiểm tra đường dẫn nhanh mà các câu trả lời khác có, và xác nhận đầu vào. Nói tóm lại, điều này nên được những câu trả lời:

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

Hoặc nếu bạn muốn trở nên siêu tuyệt vời, bạn có thể sử dụng biến thể 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;
                    }
                }
            }
        }
    }
}

bit bitconverter sẽ tốt hơn khi `` `cho (var i = 0; i <Count; i + = sizeof (long)) {if (BitConverter.ToInt64 (buffer1, i)! = BitConverter.ToInt64 (buffer2, i)) {trả về sai; }} `` `
Simon

2

Các thử nghiệm của tôi cho thấy rằng nó chắc chắn giúp gọi Stream.ReadByte () ít lần hơn, nhưng sử dụng BitConverter để gói byte không tạo ra nhiều khác biệt so với việc so sánh các byte trong một mảng byte.

Vì vậy, có thể thay thế vòng lặp "Math.Cading và lặp lại" trong nhận xét ở trên bằng cách đơn giản nhất:

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

Tôi đoán nó có liên quan đến thực tế là BitConverter.ToInt64 cần thực hiện một chút công việc (kiểm tra các đối số và sau đó thực hiện dịch chuyển bit) trước khi bạn so sánh và kết thúc là cùng một lượng công việc như so sánh 8 byte trong hai mảng .


1
Array.Equals đi sâu hơn vào hệ thống, vì vậy nó có thể sẽ nhanh hơn rất nhiều so với việc đi từng byte theo C #. Tôi không thể nói cho Microsoft, nhưng sâu xa hơn, Mono sử dụng lệnh memcpy () của C để cân bằng mảng. Không thể nhanh hơn thế nhiều.
RandomInsano

2
@RandomInsano đoán bạn có nghĩa là memcmp (), không phải memcpy ()
Cảnh sát SQL

1

Nếu các tệp không quá lớn, bạn có thể sử dụng:

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

Sẽ chỉ khả thi khi so sánh băm nếu băm là hữu ích để lưu trữ.

(Đã chỉnh sửa mã để một cái gì đó sạch sẽ hơn nhiều.)


1

Một cải tiến khác trên các tệp lớn có độ dài giống hệt nhau, có thể là không đọc các tệp một cách tuần tự, mà là so sánh các khối ngẫu nhiên nhiều hơn hoặc ít hơn.

Bạn có thể sử dụng nhiều luồng, bắt đầu trên các vị trí khác nhau trong tệp và so sánh tiến hoặc lùi.

Bằng cách này, bạn có thể phát hiện các thay đổi ở giữa / cuối tệp, nhanh hơn bạn sẽ đến đó bằng cách sử dụng cách tiếp cận tuần tự.


1
Đập đĩa sẽ gây ra vấn đề ở đây?
RandomInsano

Ổ đĩa vật lý có, SSD sẽ xử lý việc này.
TheLegendaryCopyCoder

1

Nếu bạn chỉ cần so sánh hai tệp, tôi đoán cách nhanh nhất sẽ là (trong C, tôi không biết liệu nó có áp dụng được với .NET không)

  1. mở cả hai tập tin F1, f2
  2. lấy chiều dài tập tin tương ứng l1, l2
  3. nếu l1! = l2 các tệp khác nhau; dừng lại
  4. mmap () cả hai tập tin
  5. sử dụng memcmp () trên các tệp mmap () ed

OTOH, nếu bạn cần tìm xem có các tệp trùng lặp trong một tập hợp N tệp hay không, thì cách nhanh nhất chắc chắn là sử dụng hàm băm để tránh so sánh từng bit của N-way.


1

Một cái gì đó (hy vọng) hợp lý hiệu quả:

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

Dưới đây là một số chức năng tiện ích cho phép bạn xác định xem hai tệp (hoặc hai luồng) có chứa dữ liệu giống nhau không.

Tôi đã cung cấp một phiên bản "nhanh" đa luồng vì nó so sánh các mảng byte (mỗi bộ đệm được điền từ những gì đã đọc trong mỗi tệp) trong các luồng khác nhau bằng cách sử dụng Tác vụ.

Như mong đợi, nó nhanh hơn nhiều (nhanh hơn khoảng 3 lần) nhưng nó tiêu tốn nhiều CPU hơn (vì nó đa luồng) và nhiều bộ nhớ hơn (vì nó cần hai bộ đệm mảng byte cho mỗi luồng so sánh).

    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

Tôi nghĩ có những ứng dụng mà "băm" nhanh hơn so với so sánh từng byte. Nếu bạn cần so sánh một tệp với người khác hoặc có hình thu nhỏ của ảnh có thể thay đổi. Nó phụ thuộc vào nơi và cách sử dụng.

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

Ở đây, bạn có thể nhận được những gì là nhanh nhất.

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

Tùy chọn, chúng ta có thể lưu băm trong cơ sở dữ liệu.

Hy vọng điều này có thể giúp


0

Một câu trả lời khác, bắt nguồn từ @chsh. MD5 với cách sử dụng và lối tắt cho cùng một tệp, tệp không tồn tại và độ dài khác nhau:

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

Bạn nói if (i>=secondHash.Length ...Trong trường hợp nào hai băm MD5 sẽ có độ dài khác nhau?
ếch

-1

Điều này tôi đã tìm thấy hoạt động tốt khi so sánh độ dài đầu tiên mà không cần đọc dữ liệu và sau đó so sánh chuỗi byte đọc

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)));
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.