.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.