.NET을 사용하여 2 개의 파일을 빠르게 비교하는 방법은 무엇입니까?


답변:


117

체크섬 비교는 바이트 단위 비교보다 느릴 수 있습니다.

체크섬을 생성하려면 파일의 각 바이트를로드하고 처리해야합니다. 그런 다음 두 번째 파일에서이 작업을 수행해야합니다. 비교 검사보다 처리 속도가 거의 느려집니다.

체크섬 생성 : 암호화 클래스를 사용하여이 작업을 쉽게 수행 할 수 있습니다. 다음 은 C #으로 MD5 체크섬을 생성하는 간단한 예입니다 .

그러나 "테스트"또는 "기본"사례의 체크섬을 미리 계산할 수 있으면 체크섬이 더 빠르고 더 의미가있을 수 있습니다. 기존 파일이 있고 새 파일이 기존 파일과 동일한 지 확인하려는 경우 "기존"파일의 체크섬을 사전 계산하면 DiskIO를 한 번만 수행하면됩니다. 새로운 파일. 이것은 바이트 단위 비교보다 빠를 것입니다.


30
파일이있는 위치를 고려해야합니다. 로컬 파일을 전 세계의 중간 백업 또는 끔찍한 대역폭을 가진 네트워크와 비교하는 경우 바이트 스트림을 전송하는 대신 먼저 해시하고 네트워크를 통해 체크섬을 보내는 것이 좋습니다. 비교.
— Kim

@ ReedCopsey : 비슷한 문제가 있습니다. 복제본이 많이 포함되어있는 여러 가지 정교한 작업으로 생성 된 입출력 파일을 저장해야하기 때문입니다. 사전 계산 해시를 사용하려고 생각했지만 2 (예 : MD5) 해시가 같으면 2 파일이 같고 바이트 2 바이트를 더 이상 비교하지 않는다고 합리적으로 가정 할 수 있다고 생각하십니까? 내가 아는 한 MD5 / SHA1 등의 충돌은 실제로 일어날 것 같지 않습니다 ...
— digEmAll

1
@digEmAll 충돌 가능성이 낮습니다.하지만 항상 더 강력한 해시를 수행 할 수 있습니다. 즉, SHA1 대신 SHA256을 사용하면 충돌 가능성이 더욱 줄어 듭니다.
— 리드 콥시

귀하의 답변에 감사드립니다-나는 단지 .net에 들어가고 있습니다. 해시 코드 / 체크섬 기술을 사용하는 경우 기본 폴더의 해시가 어딘가에 영구적으로 저장된다고 가정합니다. 궁금한 점이 있다면 WPF 응용 프로그램을 위해 어떻게 저장 하시겠습니까? (현재 xml, 텍스트 파일 또는 데이터베이스를보고 있습니다).
— BKSpurgeon

139

가장 느린 방법은 두 파일을 바이트 단위로 비교하는 것입니다. 내가 찾은 가장 빠른 것은 비슷한 비교이지만 한 번에 1 바이트 대신 Int64 크기의 바이트 배열을 사용하고 결과 숫자를 비교합니다.

내가 생각해 낸 것은 다음과 같습니다.

    const int BYTES_TO_READ = sizeof(Int64);

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

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

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

        using (FileStream fs1 = first.OpenRead())
        using (FileStream fs2 = second.OpenRead())
        {
            byte[] one = new byte[BYTES_TO_READ];
            byte[] two = new byte[BYTES_TO_READ];

            for (int i = 0; i < iterations; i++)
            {
                 fs1.Read(one, 0, BYTES_TO_READ);
                 fs2.Read(two, 0, BYTES_TO_READ);

                if (BitConverter.ToInt64(one,0) != BitConverter.ToInt64(two,0))
                    return false;
            }
        }

        return true;
    }

필자의 테스트에서 이는 간단한 3 : 1만큼 간단한 ReadByte () 시나리오보다 성능이 뛰어남을 알 수있었습니다. 평균 1000 회 이상 실행되면 1063ms 에서이 방법을 얻었고 3031ms에서 아래의 방법 (바이트 단위로 빠른 비교)을 얻었습니다. 해싱은 항상 평균 865ms에서 1 초 미만으로 돌아 왔습니다. 이 테스트는 ~ 100MB 비디오 파일로 수행되었습니다.

비교 목적으로 사용한 ReadByte 및 해싱 방법은 다음과 같습니다.

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

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

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

        return true;
    }

    static bool FilesAreEqual_Hash(FileInfo first, FileInfo second)
    {
        byte[] firstHash = MD5.Create().ComputeHash(first.OpenRead());
        byte[] secondHash = MD5.Create().ComputeHash(second.OpenRead());

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

1
당신은 내 인생을 더 쉽게 만들었습니다. 감사합니다
— anindis

2
@anindis : 완전성을 위해 @Lars 'answer 및 @ RandomInsano 's answer를 모두 읽을 수 있습니다 . 그래도 오랜 세월 동안 도움이되어 다행입니다! :)
— chsh

1
이 FilesAreEqual_Hash메소드는 메소드 using와 마찬가지로 두 파일 스트림 모두에 있어야 ReadByte합니다. 그렇지 않으면 두 파일 모두에 정지됩니다.
— 이안 머서

2
참고 FileStream.Read()실제로 요청 된 수보다 적은 바이트를 읽을 수 있습니다. StreamReader.ReadBlock()대신 사용해야 합니다.
— Palec

2
스트림 길이가 Int64의 배수가 아닌 Int64 버전에서 마지막 반복은 이전 반복의 채우기를 사용하여 채워지지 않은 바이트를 비교합니다 (동일해야하므로 괜찮습니다). 또한 스트림 길이가 sizeof (Int64)보다 작 으면 C #이 배열을 초기화하므로 채워지지 않은 바이트는 0입니다. IMO, 코드는 아마도 이러한 이상한 점을 언급해야합니다.
— crokusek

46

만약 당신 이 진정으로 전체 바이트 단위 비교 가 필요하다고 결정한다면 (해싱에 대한 다른 답변을보십시오) 가장 쉬운 해결책은 다음과 같습니다 :


• 예를 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)));


• System.String경로 이름 :

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


게시 된 다른 답변과 달리 이것은 바이너리, 텍스트, 미디어, 실행 파일 등 모든 종류의 파일에 대해 결정적으로 정확 하지만 완전한 바이너리 비교 로 "중요하지 않은"방법 만 다른 파일 (예 : BOM , 줄) -ending , 문자 인코딩 , 미디어 메타 데이터, 공백, 패딩, 소스 코드 주석 등은 항상 같지 않은 것으로 간주됩니다 .

이 코드는 두 파일을 모두 메모리에 완전히로드하므로 실제로 거대한 파일 을 비교하는 데 사용 해서는 안됩니다 . 중요주의를 넘어, 전체로드는 .NET의 디자인 주어진 페널티 킥 정말 아니다 GC는 (그것은 근본적으로 작은 유지하기 위해 최적화 있기 때문에 단명 할당 매우 싼 ), 때 사실도 최적이 될 수 파일 크기가 예상된다 이상이어야 85K (다음과 같이) 사용자의 최소한의 코드를 사용하여 최대한으로 파일 성능 문제를 위임하는 것을 의미하기 때문에, CLR, BCL, 및 JIT(예를 들어) 최신 설계 기술, 시스템 코드, 및 적응 런타임 최적화의 혜택을.

또한 이러한 임시 시나리오의 경우 LINQ열거자를 통해 바이트 단위 비교 성능에 대한 우려가 불분명합니다 ( 파일 I / O에 대해 디스크 ahitt̲ a̲l̲l̲ 를 때리면 몇 배씩 줄어듦). 다양한 메모리 비교 대안 중 하나. 예를 들어, 비록 SequenceEqual 않는 사실 우리의 "최적화"제공 첫 불일치에 포기를 이 거의 파일 '내용을 이미 가지고 가져온 후, 각 완전히 필요한 경기를 확인하기 위해 중요하지 ..


3
이것은 큰 파일에는 좋지 않습니다. 바이트 배열 비교를 시작하기 전에 두 파일을 모두 읽으므로 메모리 사용량에 좋지 않습니다. 그래서 버퍼가있는 스트림 리더를 선호합니다.
— Krypto_47

3
@ Krypto_47 나는 이러한 요소들과 대답의 텍스트에서 적절하게 사용되는 것에 대해 논의했습니다.
— Glenn Slayden

33

리드 콥시 의 답변 외에도 :

  • 최악의 경우 두 파일이 동일합니다. 이 경우 파일을 바이트 단위로 비교하는 것이 가장 좋습니다.

  • 두 파일이 동일하지 않으면 파일이 동일하지 않다는 것을 빨리 감지하여 속도를 높일 수 있습니다.

예를 들어, 두 파일의 길이가 다른 경우 파일이 동일 할 수 없으며 실제 내용을 비교할 필요조차 없다는 것을 알 수 있습니다.


10
완료하려면 : 1 위치의 바이트가 다르면 다른 큰 게인이 중지됩니다.
— Henk Holterman

6
@Henk :이 :-) 너무 분명했다 생각
— DTB

1
이것을 추가하는 것이 좋습니다. 그것은 나에게 명백했기 때문에 그것을 포함시키지 않았지만 언급하는 것이 좋습니다.
— Reed Copsey가

16

작은 8 바이트 청크를 읽지 않고 더 큰 청크를 읽으면 루프를 돌리면 훨씬 빨라집니다. 평균 비교 시간을 1/4로 줄였습니다.

    public static bool FilesContentsAreEqual(FileInfo fileInfo1, FileInfo fileInfo2)
    {
        bool result;

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

        return result;
    }

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

        while (true)
        {
            int count1 = stream1.Read(buffer1, 0, bufferSize);
            int count2 = stream2.Read(buffer2, 0, bufferSize);

            if (count1 != count2)
            {
                return false;
            }

            if (count1 == 0)
            {
                return true;
            }

            int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64));
            for (int i = 0; i < iterations; i++)
            {
                if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64)))
                {
                    return false;
                }
            }
        }
    }
}

13
일반적으로 확인 count1 != count2이 올바르지 않습니다. Stream.Read()여러 가지 이유로 제공 한 수보다 적은 수를 반환 할 수 있습니다.
— porges

1
버퍼가 짝수의 Int64블록을 보유하게하려면 다음과 같이 크기를 계산할 수 있습니다 const int bufferSize = 1024 * sizeof(Int64).
— Jack A.

14

바이트 단위 비교보다 체크섬 비교가 약간 더 빠를 수있는 유일한 방법은 한 번에 하나의 파일을 읽고 디스크 헤드의 탐색 시간을 다소 단축한다는 것입니다. 그러나 약간의 이득은 해시 계산에 추가 된 시간에 의해 매우 잘 흡수 될 수 있습니다.

또한 파일의 체크섬 비교는 파일이 동일한 경우에만 더 빠를 가능성이 있습니다. 그렇지 않은 경우, 바이트 별 비교는 첫 번째 차이로 끝나서 훨씬 빠릅니다.

또한 해시 코드 비교 는 파일이 동일 할 가능성 이 높다는 것을 알려줍니다 . 100 % 확실하게하려면 바이트 단위 비교를 수행해야합니다.

예를 들어 해시 코드가 32 비트 인 경우 해시 코드가 일치하면 파일이 동일한 지 약 99.99999998 %입니다. 100 %에 가깝지만 100 % 확실성이 필요한 경우에는 그렇지 않습니다.


더 큰 해시를 사용하면 테스트를 수행하는 동안 컴퓨터에 오류가 발생했을 때보 다 오 탐지 확률이 높아질 수 있습니다.
— Loren Pechtel

해시 시간과 탐색 시간에 대해 동의하지 않습니다. 단일 헤드 탐색 동안 많은 계산을 수행 할 수 있습니다 . 파일이 일치 할 확률이 높으면 많은 비트가있는 해시를 사용합니다. 만약 일치 할 가능성이 있다면 1MB 블록과 같이 한 번에 한 블록 씩 비교할 것입니다. (섹터를 분할하지 않도록 4k로 균등하게 분할하는 블록 크기를 선택하십시오.)
— Loren Pechtel

1
@Guffa의 그림 99.99999998 %를 설명하기 위해 1 - (1 / (2^32))이는 컴퓨팅 에서 비롯된 것으로 , 단일 파일에 32 비트 해시가 주어질 가능성이 있습니다. 첫 번째 파일이 "주어진"해시 값을 제공하고 다른 파일이 해당 값과 일치하는지 여부 만 고려하면되기 때문에 두 개의 서로 다른 파일이 동일한 해시를 가질 확률은 동일합니다. 64 비트 및 128 비트 해싱의 가능성은 99.999999999999999994 % 및 99.99999999999999999999999999999999999999997 %로 감소합니다.
— Glenn Slayden 23.28에

... 실제로이 숫자가 추정 적으로 단순한 개념보다 이해하기 어렵다는 사실은 사실이지만 "동일한 해시 코드에 충돌하는 무한히 많은 파일"은 인간이 왜 해시를 받아들이 는 데 합리적으로 의심 스러운지 설명 할 수 있습니다. 평등.
— Glenn Slayden

13

편집 : 이 방법 것 없다 이진 파일을 비교 !

.NET 4.0에서 File클래스에는 다음 두 가지 새로운 메소드가 있습니다.

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

다음을 사용할 수 있음을 의미합니다.

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

1
@ dtb : 바이너리 파일에서는 작동하지 않습니다. 내가 깨달았을 때 이미 의견을 입력하고 내 게시물 맨 위에 편집을 추가했을 것입니다. : o
— Sam Harwell

280Z28 @ : 난 아무것도 ;-) 말하지 않았다
— DTB

두 파일을 모두 메모리에 저장하지 않아도됩니까?
— RandomInsano

File에는 또한 SequenceEquals를 사용할 수있는 ReadAllBytes 기능이 있으므로 모든 파일에서 작동하는 것처럼 대신 사용하십시오. 그리고 @RandomInsano가 말했듯이, 이것은 메모리에 저장되므로 작은 파일에 사용하는 것이 좋지만 큰 파일에는 신중하게 사용해야합니다.
— DaedalusAlpha

1
@DaedalusAlpha 열거 형을 반환하므로, 라인은 주문형으로로드되고 메모리에 항상 저장되지 않습니다. 반면에 ReadAllBytes는 전체 파일을 배열로 반환합니다.
— IllidanS4는 Monica를

7

솔직히 검색 트리를 최대한 정리해야한다고 생각합니다.

바이트 단위로 가기 전에 확인해야 할 사항 :

  1. 크기가 동일합니까?
  2. 파일 A의 마지막 바이트가 파일 B와 다릅니다

또한 드라이브가 순차적 바이트를보다 빨리 읽으므로 한 번에 큰 블록을 읽는 것이 더 효율적입니다. 바이트 단위로 이동하면 시스템 호출이 훨씬 많아 질뿐 아니라 두 파일이 모두 같은 드라이브에있는 경우 기존 하드 드라이브의 읽기 헤드가 더 자주 검색됩니다.

청크 A와 청크 B를 바이트 버퍼로 읽고 비교하십시오 (배열을 사용하지 마십시오. 주석 참조). 메모리와 성능 사이에서 균형이 잘 맞다고 생각 될 때까지 블록의 크기를 조정하십시오. 비교를 멀티 스레딩 할 수도 있지만 디스크 읽기를 멀티 스레딩하지 마십시오.


Array.Equals를 사용하면 전체 배열을 비교하므로 나쁜 생각입니다. 적어도 하나의 블록 읽기가 전체 배열을 채우지 못할 수 있습니다.
— Doug Clutter

왜 전체 배열을 비교하는 것이 나쁜 생각입니까? 블록 읽기가 배열을 채우지 않는 이유는 무엇입니까? 확실히 좋은 튜닝 포인트가 있지만 크기를 가지고 노는 이유입니다. 별도의 스레드에서 비교를위한 추가 포인트.
— RandomInsano

바이트 배열을 정의하면 길이가 고정됩니다. (예 : var buffer = new byte [4096]) 파일에서 블록을 읽을 때 전체 4096 바이트를 반환하거나 반환하지 않을 수 있습니다. 예를 들어 파일의 길이가 3000 바이트 인 경우
— Doug Clutter

아, 이제 이해합니다! 좋은 소식은 읽기가 배열에로드 된 바이트 수를 반환하므로 배열을 채울 수 없으면 데이터가 있다는 것입니다. 우리는 동등성을 테스트하고 있기 때문에 오래된 버퍼 데이터는 중요하지 않습니다. 문서 : msdn.microsoft.com/en-us/library/9kstw824(v=vs.110).aspx
— RandomInsano

또한 Equals () 메서드를 사용하는 것이 좋습니다. Mono에서는 요소가 메모리에서 연속적이므로 메모리를 비교합니다. 그러나 Microsoft는이를 무시하지 않고 항상 거짓 인 참조 비교 만 수행합니다.
— RandomInsano

4

내 대답은 @lars의 파생이지만에 대한 호출에서 버그를 수정합니다 Stream.Read. 또한 다른 답변의 빠른 경로 확인 및 입력 유효성 검사를 추가합니다. 즉,이해야 대답 :

using System;
using System.IO;

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

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

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

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

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

        private static int ReadFullBuffer(Stream stream, byte[] buffer)
        {
            int bytesRead = 0;
            while (bytesRead < buffer.Length)
            {
                int read = stream.Read(buffer, bytesRead, buffer.Length - bytesRead);
                if (read == 0)
                {
                    // Reached end of stream.
                    return bytesRead;
                }

                bytesRead += read;
            }

            return bytesRead;
        }

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

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

                if (count1 != count2)
                {
                    return false;
                }

                if (count1 == 0)
                {
                    return true;
                }

                int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64));
                for (int i = 0; i < iterations; i++)
                {
                    if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64)))
                    {
                        return false;
                    }
                }
            }
        }
    }
}

또는 대단하고 싶다면 비동기 변형을 사용할 수 있습니다.

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

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

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

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

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

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

        private static async Task<int> ReadFullBufferAsync(Stream stream, byte[] buffer)
        {
            int bytesRead = 0;
            while (bytesRead < buffer.Length)
            {
                int read = await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead).ConfigureAwait(false);
                if (read == 0)
                {
                    // Reached end of stream.
                    return bytesRead;
                }

                bytesRead += read;
            }

            return bytesRead;
        }

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

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

                if (count1 != count2)
                {
                    return false;
                }

                if (count1 == 0)
                {
                    return true;
                }

                int iterations = (int)Math.Ceiling((double)count1 / sizeof(Int64));
                for (int i = 0; i < iterations; i++)
                {
                    if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64)))
                    {
                        return false;
                    }
                }
            }
        }
    }
}

(var i = 0; i <count; i + = sizeof (long)) {if (BitConverter.ToInt64 (buffer1, i)! = BitConverter.ToInt64 (buffer2, i))에 대해 비트 컨버터 비트가```로 더 나을 것입니다. {거짓을 반환; }}```
— Simon

2

필자의 실험에 따르면 Stream.ReadByte ()를 더 적은 횟수로 호출하는 것이 확실히 도움이되지만 BitConverter를 사용하여 바이트를 패키지하면 바이트 배열의 바이트를 비교하는 데 큰 차이가 없습니다.

따라서 위 주석에서 "Math.Ceiling and iterations"루프를 가장 간단한 것으로 바꿀 수 있습니다.

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

BitConverter.ToInt64는 비교하기 전에 약간의 작업 (인수를 확인한 다음 비트 이동을 수행해야 함)이 필요하다는 사실과 관련이 있다고 생각합니다. .


1
Array.Equals는 시스템에 더 깊이 들어가므로 C #에서 바이트 단위로 이동하는 것보다 훨씬 빠릅니다. Microsoft에 대해서는 말할 수 없지만 Mono는 C의 memcpy () 명령을 배열 평등에 사용합니다. 그것보다 훨씬 빨리 얻을 수 없습니다.
— RandomInsano

2
@RandomInsano는 memcpy ()가 아니라 memcmp ()를 의미한다고 생각합니다.
— SQL Police

1

파일이 너무 크지 않으면 다음을 사용할 수 있습니다.

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

해시를 저장하는 것이 유용한 경우에만 해시를 비교하는 것이 가능합니다.

(코드를 훨씬 더 깨끗한 것으로 편집했습니다.)


1

길이가 같은 큰 파일의 또 다른 개선점은 파일을 순차적으로 읽지 않고 임의의 블록을 비교하는 것입니다.

파일의 다른 위치에서 시작하여 앞뒤로 비교하는 여러 스레드를 사용할 수 있습니다.

이렇게하면 순차적 접근 방식을 사용하는 것보다 빠르게 파일의 중간 / 끝에서 변경 사항을 감지 할 수 있습니다.


1
디스크 스 래싱으로 인해 문제가 발생합니까?
— RandomInsano

물리적 디스크 드라이브, 예. SSD가이를 처리합니다.
— TheLegendaryCopyCoder

1

두 파일 만 비교 해야하는 경우 가장 빠른 방법은 다음과 같습니다 (C에서는 .NET에 해당되는지 알 수 없음)

  1. 두 파일 f1, f2를 엽니 다
  2. 각각의 파일 길이 l1, l2를 얻습니다.
  3. l1! = l2이면 파일이 다릅니다. 중지
  4. mmap () 두 파일
  5. mmap () 파일에서 memcmp () 사용

OTOH, N 파일 세트에 중복 파일이 있는지 확인 해야하는 경우 가장 빠른 방법은 N- 비트 비트 비교를 피하기 위해 해시를 사용하는 것입니다.


1

합리적으로 효율적인 것 :

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

다음은 두 개의 파일 (또는 두 개의 스트림)에 동일한 데이터가 포함되어 있는지 확인할 수있는 유틸리티 기능입니다.

Tasks를 사용하여 다른 스레드에서 바이트 배열 (각 파일에서 읽은 내용으로 채워진 각 버퍼)을 비교할 때 다중 스레드 인 "빠른"버전을 제공했습니다.

예상대로 훨씬 빠르지 만 (약 3 배 더 빠름) 더 많은 CPU (멀티 스레드이기 때문에)와 더 많은 메모리를 소비합니다 (비교 스레드 당 2 바이트 배열 버퍼가 필요하기 때문에).

    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

"해시"가 바이트 단위로 비교하는 것보다 빠른 응용 프로그램이 있다고 생각합니다. 파일을 다른 파일과 비교해야하거나 변경할 수있는 사진의 썸네일이있는 경우. 사용 위치와 방법에 따라 다릅니다.

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

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

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

여기서 가장 빠른 것을 얻을 수 있습니다.

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

선택적으로 해시를 데이터베이스에 저장할 수 있습니다.

이것이 도움이되기를 바랍니다.


0

@chsh에서 파생 된 또 다른 대답. 파일에 대한 사용 및 바로 가기가있는 MD5, 파일이 존재하지 않으며 길이가 다릅니다.

/// <summary>
/// Performs an md5 on the content of both files and returns true if
/// they match
/// </summary>
/// <param name="file1">first file</param>
/// <param name="file2">second file</param>
/// <returns>true if the contents of the two files is the same, false otherwise</returns>
public static bool IsSameContent(string file1, string file2)
{
    if (file1 == file2)
        return true;

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

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

    using (FileStream file1Stream = file1Info.OpenRead())
    using (FileStream file2Stream = file2Info.OpenRead())
    { 
        byte[] firstHash = MD5.Create().ComputeHash(file1Stream);
        byte[] secondHash = MD5.Create().ComputeHash(file2Stream);
        for (int i = 0; i < firstHash.Length; i++)
        {
            if (i>=secondHash.Length||firstHash[i] != secondHash[i])
                return false;
        }
        return true;
    }
}

당신은 말을 if (i>=secondHash.Length ...어떤 상황에서 두 MD5 해시 길이가 서로 다른 것입니까?
— frogpelt 2016 년

-1

이것은 데이터를 읽지 않고 먼저 길이를 비교 한 다음 읽은 바이트 시퀀스를 비교하는 것이 효과적이라는 것을 알았습니다.

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)));
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.