.NET का उपयोग करके तेजी से 2 फ़ाइलों की तुलना कैसे करें?


136

विशिष्ट दृष्टिकोण फाइलस्ट्रीम के माध्यम से बाइनरी को पढ़ने और इसे बाइट-बाइट की तुलना करने की सलाह देते हैं।

  • क्या सीआरसी जैसी कोई चेकसम तुलना तेज होगी?
  • क्या कोई .NET लाइब्रेरीज़ हैं जो किसी फ़ाइल के लिए एक चेकसम उत्पन्न कर सकती हैं?

जवाबों:


117

एक चेकसम तुलना बाइट-बाइट तुलना की तुलना में सबसे धीमी होने की संभावना होगी।

चेकसम उत्पन्न करने के लिए, आपको फ़ाइल के प्रत्येक बाइट को लोड करना होगा, और उस पर प्रसंस्करण करना होगा। फिर आपको दूसरी फाइल पर यह करना होगा। तुलनात्मक जाँच की तुलना में प्रसंस्करण निश्चित रूप से धीमा होगा।

चेकसम उत्पन्न करने के लिए: आप क्रिप्टोग्राफी कक्षाओं के साथ आसानी से कर सकते हैं। यहाँ C # के साथ MD5 चेकसम उत्पन्न करने का एक छोटा उदाहरण है

हालाँकि, एक चेकसम तेजी से हो सकता है और अधिक समझ में आता है यदि आप "परीक्षण" या "आधार" मामले के चेकसम की पूर्व गणना कर सकते हैं। यदि आपके पास एक मौजूदा फ़ाइल है, और आप यह देखने के लिए जाँच कर रहे हैं कि क्या कोई नई फ़ाइल मौजूदा के समान है, तो आपकी "मौजूदा" फ़ाइल पर चेकसम को प्री-कंप्यूटिंग करने का मतलब केवल डिस्कियो को एक बार करने की आवश्यकता होगी। नई फ़ाइल। यह संभवतः बाइट-बाइट तुलना की तुलना में तेज़ होगा।


30
सुनिश्चित करें कि आपकी फ़ाइलें कहाँ स्थित हैं, इसे ध्यान में रखें। यदि आप स्थानीय फ़ाइलों की तुलना दुनिया भर में आधे-अधूरे तरीके से (या भयानक बैंडविड्थ वाले नेटवर्क पर) कर रहे हैं, तो आप पहले हैश करने के लिए बेहतर हो सकते हैं और बाइट्स की एक धारा भेजने के बजाय नेटवर्क पर एक चेकसम भेज सकते हैं की तुलना करें।
किम

@ReedCopsey: मुझे एक ऐसी ही समस्या हो रही है, क्योंकि मुझे कई विस्तृत विवरणों द्वारा निर्मित इनपुट / आउटपुट फ़ाइलों को संग्रहीत करने की आवश्यकता है, जिनमें बहुत सारे दोहराव शामिल हैं। मैंने सोचा था कि पूर्व-निर्मित हैश का उपयोग करें, लेकिन क्या आपको लगता है कि मैं यथोचित मान सकता हूं कि यदि 2 (जैसे एमडी 5) हैश बराबर हैं, तो 2 फाइलें बराबर हैं और आगे की बाइट-2-बाइट की तुलना से बचें? जहाँ तक मुझे पता है MD5 / SHA1 इत्यादि टकराव वास्तव में संभावना नहीं है ...
डाइजेक्सिल

1
@digEmAll टक्कर की संभावना कम है - आप हमेशा एक मजबूत हैश कर सकते हैं, हालांकि - यानी: SHA1 के बजाय SHA256 का उपयोग करें, जो टकराव की संभावना को और कम कर देगा।
रीड कोपसी

आपके उत्तर के लिए धन्यवाद - मैं बस .net में जा रहा हूँ मुझे लगता है कि अगर कोई हैशकोड / चेक राशि तकनीक का उपयोग कर रहा है, तो मुख्य फ़ोल्डर का हैश कहीं लगातार जमा हो जाएगा? जिज्ञासा से बाहर आप इसे WPF एप्लिकेशन के लिए कैसे संग्रहीत करेंगे - आप क्या करेंगे? (मैं वर्तमान में xml, पाठ फ़ाइलों या डेटाबेस को देख रहा हूं)।
बीकेएसपर्जन

139

सबसे धीमी संभव विधि दो फाइलों को बाइट द्वारा तुलना करना है। मैं जिस तेज़ी से आने में सक्षम था, वह एक समान तुलना है, लेकिन एक बार में एक बाइट के बजाय, आप बाइट्स आकार की एक सरणी का उपयोग 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 तक एक सीधा रीडबाइट () परिदृश्य के इस बेहतर प्रदर्शन को देख पा रहा था। 1000 से अधिक रनों से अधिक, मुझे यह विधि 1063ms में मिली, और नीचे की विधि (बाइट तुलना द्वारा सीधी बाइट) 3031ms पर। हैशिंग हमेशा उप-सेकंड में लगभग 865ms के औसत पर वापस आया। यह परीक्षण ~ 100MB वीडियो फ़ाइल के साथ था।

तुलना उद्देश्यों के लिए मैंने ReadByte और hashing विधियों का उपयोग किया है:

    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 के उत्तर और @ RandomInsano के उत्तर दोनों को पढ़ना चाह सकते हैं । खुशी है कि इसने इतने वर्षों में मदद की! :)
चिश

1
FilesAreEqual_Hashविधि एक होना चाहिए usingदोनों फाइल पर भी तरह धाराओं ReadByteविधि अन्यथा यह दोनों फ़ाइलों के लिए पर रखती हूँ।
इयान मर्सर

2
ध्यान दें कि FileStream.Read()वास्तव में अनुरोधित संख्या से कम बाइट्स पढ़ सकते हैं। आपको StreamReader.ReadBlock()इसके बजाय उपयोग करना चाहिए ।
पालेक

2
Int64 संस्करण में जब धारा की लंबाई Int64 की एक से अधिक नहीं होती है, तो अंतिम पुनरावृत्ति पिछले पुनरावृत्ति के भराव का उपयोग करके अनफ़िल्टर्ड बाइट्स की तुलना कर रही है (जो कि समान होना चाहिए इसलिए यह ठीक है)। इसके अलावा, यदि धारा की लंबाई साइजोफ़ (Int64) से कम है, तो अनफ़िल्टर्ड बाइट्स 0 हैं क्योंकि C # सरणियों को आरंभ करता है। IMO, कोड को शायद इन विषमताओं पर टिप्पणी करनी चाहिए।
क्रोकेक

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


कुछ अन्य पोस्ट किए गए उत्तरों के विपरीत, यह किसी भी तरह की फ़ाइल के लिए निर्णायक रूप से सही है : बाइनरी, टेक्स्ट, मीडिया, निष्पादन योग्य, आदि, लेकिन एक पूर्ण बाइनरी तुलना के रूप में , फाइलें जो केवल "महत्वहीन" तरीकों (जैसे बीओएम , लाइन) में भिन्न होती हैं -फेंडिंग , कैरेक्टर एन्कोडिंग , मीडिया मेटाडेटा, व्हॉट्सएप , पेडिंग, सोर्स-कोड कमेंट्स आदि) को हमेशा न के बराबर माना जाएगा ।

यह कोड पूरी तरह से मेमोरी में दोनों फाइलों को लोड करता है, इसलिए इसका उपयोग वास्तव में विशाल फाइलों की तुलना करने के लिए नहीं किया जाना चाहिए । उस महत्वपूर्ण कैविएट से परे, पूर्ण लोडिंग वास्तव में .NET GC के डिजाइन को देखते हुए जुर्माना नहीं दिया गया है (क्योंकि यह छोटे, अल्पकालिक आबंटन को बहुत सस्ता रखने के लिए अनुकूलित है ), और वास्तव में फ़ाइल आकार अपेक्षित होने पर भी इष्टतम हो सकता है । की तुलना में कम होने के लिए 85K , क्योंकि (जैसा कि यहाँ दिखाया) उपयोगकर्ता कोड की एक न्यूनतम का उपयोग कर अधिकतम तात्पर्य करने के लिए फ़ाइल निष्पादन के मुद्दे सौंपने CLR, BCLऔरJIT (जैसे) नवीनतम डिजाइन प्रौद्योगिकी, सिस्टम कोड, और अनुकूली क्रम ऑप्टिमाइज़ेशन से लाभ प्राप्त करने के लिए।

इसके अलावा, ऐसे कार्यदिवस परिदृश्यों के लिए, LINQएन्यूमरेटर्स (जैसा कि यहां दिखाया गया है) के माध्यम से बाइट-बाइट तुलना के प्रदर्शन के बारे में चिंताएं हैं, क्योंकि फ़ाइल I / O के लिए डिस्क a̲t̲ a̲l̲l̲ मारने के बाद , परिमाण के कई आदेशों से, लाभ होगा। विभिन्न स्मृति-तुलना विकल्पों के लिए। उदाहरण के लिए, भले ही SequenceEqual करता है वास्तव में हम में से "अनुकूलन" देने के पहले बेमेल पर छोड़ , इस मुश्किल से पहले से ही हो रही फ़ाइलों की सामग्री दिलवाया के बाद, प्रत्येक पूरी तरह से आवश्यक मैच पुष्टि करने के लिए मायने रखती है ..


3
यह बड़ी फ़ाइलों के लिए अच्छा नहीं लगता है। मेमोरी उपयोग के लिए अच्छा नहीं है क्योंकि यह बाइट सरणी की तुलना करने से पहले दोनों फाइलों को अंत तक पढ़ेगा। यही कारण है कि मैं एक बफर के साथ एक स्ट्रीमरडर के लिए जाना होगा।
क्रिप्टो_47

3
@ Krypto_47 मैंने अपने जवाब के पाठ में इन कारकों और ऐपप्रेट के उपयोग पर चर्चा की।
ग्लेन स्लेडेन

33

के अलावा रीड Copsey के जवाब:

  • सबसे बुरी स्थिति यह है कि दो फाइलें समान हैं। इस मामले में फाइलों को बाइट-बाइट के साथ तुलना करना सबसे अच्छा है।

  • यदि दो फाइलें एक जैसी नहीं हैं, तो आप चीजों का पता लगाकर तेजी से गति बढ़ा सकते हैं कि वे समान नहीं हैं।

उदाहरण के लिए, यदि दो फाइलें अलग-अलग लंबाई की हैं, तो आप जानते हैं कि वे समान नहीं हो सकती हैं, और आपको उनकी वास्तविक सामग्री की तुलना भी नहीं करनी है।


10
पूरा होने के लिए: 1 स्थान पर बाइट्स अलग होते ही दूसरा बड़ा लाभ रुक रहा है।
हेंक होल्टरमैन

6
@ हेनक: मुझे लगा कि यह बहुत स्पष्ट था :-)
dtb

1
इसे जोड़ने पर अच्छी बात है। यह मेरे लिए स्पष्ट था, इसलिए मैंने इसे शामिल नहीं किया, लेकिन इसका उल्लेख करना अच्छा है।
रीड कोपसे

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()विभिन्न कारणों से, आपके द्वारा प्रदान की गई गणना से कम वापस आ सकता है।
पोर्स

1
यह सुनिश्चित करने के लिए कि बफर एक समान संख्या में Int64ब्लॉक रखेगा , आप इस तरह आकार की गणना करना चाहते हैं const int bufferSize = 1024 * sizeof(Int64):।
जैक ए।

14

केवल एक चीज जो बाइट-बाय-बाइट तुलना की तुलना में एक चेकसम तुलना को थोड़ा तेज कर सकती है, वह यह है कि आप एक बार में एक फ़ाइल पढ़ रहे हैं, कुछ हद तक डिस्क सिर के लिए समय कम कर रहा है। हालांकि हैश की गणना के अतिरिक्त समय के हिसाब से उस मामूली लाभ को बहुत अच्छी तरह से खाया जा सकता है।

साथ ही, एक चेकसम की तुलना बेशक केवल फ़ाइलों के समान होने पर तेजी से होने का कोई मौका हो। यदि वे नहीं हैं, तो एक बाइट-बाय-बाइट तुलना पहले अंतर पर समाप्त हो जाएगी, जिससे यह बहुत तेज हो जाएगा।

आपको यह भी विचार करना चाहिए कि एक हैश कोड तुलना केवल आपको बताती है कि यह बहुत संभावना है कि फाइलें समान हैं। 100% निश्चित होने के लिए आपको एक बाइट-बाय-बाइट तुलना करने की आवश्यकता है।

यदि उदाहरण के लिए हैश कोड 32 बिट्स है, तो आप लगभग 99.99999998% निश्चित हैं कि फाइलें हैश कोड से मेल खाती हैं तो समान हैं। यह 100% के करीब है, लेकिन अगर आपको वास्तव में 100% निश्चितता की आवश्यकता है, तो यह नहीं है।


एक बड़े हैश का उपयोग करें और आप परीक्षण करते समय कंप्यूटर द्वारा मिटाए गए बाधाओं से अच्छी तरह से एक झूठी सकारात्मक की बाधाओं को प्राप्त कर सकते हैं।
लोरेन Pechtel

मैं हैश समय बनाम समय की तलाश के बारे में असहमत हूं। आप एक ही सिर की तलाश के दौरान बहुत सारी गणना कर सकते हैं । यदि ऑड्स अधिक हैं, तो फाइलें मेल खाती हैं मैं बहुत सारे बिट्स के साथ हैश का उपयोग करता हूं। यदि एक मैच का एक उचित मौका है तो मैं उन्हें एक बार में एक ब्लॉक की तुलना करूंगा, 1MB ब्लॉक के लिए। (एक ब्लॉक का आकार चुनें जो 4k समान रूप से विभाजित करता है ताकि यह सुनिश्चित हो सके कि आप कभी भी सेक्टर विभाजित न करें।)
लोरेन Pechtel

1
@ गुफ़ा के आंकड़े 99.99999998% की व्याख्या करने के लिए, यह कंप्यूटिंग से आता है 1 - (1 / (2^32)), जो इस बात की संभावना है कि किसी एक फ़ाइल में कुछ 32-बिट हैश होगा। एक ही हैश होने वाली दो अलग-अलग फ़ाइलों की संभावना समान है, क्योंकि पहली फ़ाइल "दिया गया" हैश मान प्रदान करती है, और हमें केवल इस बात पर विचार करने की आवश्यकता है कि क्या अन्य फ़ाइल उस मान से मेल खाती है या नहीं। 64- और 128-बिट हैशिंग के साथ संभावना 99.999999999999999994% और 99.999999999999999999999999999999997% (क्रमशः) में घटती है, जैसे कि इस तरह के थैथोमेबल नंबरों के साथ।
ग्लेन स्लेडेन

... दरअसल, तथ्य यह है कि ये संख्या ज्यादातर लोगों के लिए मुश्किल से साधारण धारणा से ग्रसने के लिए कठिन होती है, हालांकि, सच है, "असीम रूप से एक ही हैश कोड में टकरा रही कई फाइलें" बता सकती हैं कि मानव अनुचित रूप से हैश-एज़ को स्वीकार करने के लिए क्यों संदिग्ध हैं? समानता।
ग्लेन स्लेडेन सेप

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
सैम हैरवेल

@ 280Z28: मैंने कुछ नहीं कहा ;-)
dtb

क्या आपको मेमोरी में दोनों फाइलों को स्टोर करने की आवश्यकता नहीं होगी?
रैंडमइन्सानो

ध्यान दें कि फ़ाइल में फ़ंक्शन ReadAllBytes भी है जो SequenceEquals का उपयोग कर सकता है और साथ ही साथ इसका उपयोग भी कर सकता है क्योंकि यह सभी फ़ाइलों पर काम करेगा। जैसा कि @RandomInsano ने कहा, यह मेमोरी में स्टोर किया जाता है, जबकि छोटी फाइलों के लिए इसका उपयोग करना ठीक है, मैं बड़ी फाइलों के साथ इसका उपयोग करने में सावधान रहूंगा।
DaedalusAlpha

1
@DaedalusAlpha यह एक enumerable देता है, इसलिए लाइनों को ऑन-डिमांड लोड किया जाएगा और पूरे समय मेमोरी में संग्रहीत नहीं किया जाएगा। दूसरी ओर ReadAllBytes, एक सरणी के रूप में पूरी फाइल लौटाता है।
IllidanS4 मोनिका को

7

ईमानदारी से, मुझे लगता है कि आपको अपने खोज पेड़ को जितना संभव हो सके नीचे गिराने की आवश्यकता है।

बाइट-बाइट जाने से पहले जाँचने योग्य बातें:

  1. आकार समान हैं?
  2. फ़ाइल में अंतिम बाइट फ़ाइल B से भिन्न है

इसके अलावा, एक बार में बड़े ब्लॉक पढ़ना अधिक कुशल होगा क्योंकि ड्राइव अनुक्रमिक बाइट्स को अधिक तेज़ी से पढ़ते हैं। बाइट-बाय-बाइट जाने से न केवल अधिक सिस्टम कॉल का कारण बनता है, बल्कि यह एक पारंपरिक हार्ड ड्राइव के रीड हेड का कारण बनता है ताकि दोनों फाइलें एक ही ड्राइव पर हो।

चंक ए और चंक बी को बाइट बफर में पढ़ें, और उनकी तुलना करें (Array.Equals का उपयोग न करें, टिप्पणियां देखें)। ब्लॉक के आकार को ट्यून करें जब तक आप हिट नहीं करते हैं जो आपको लगता है कि स्मृति और प्रदर्शन के बीच एक अच्छा व्यापार है। आप तुलना को बहु-सूत्र भी कर सकते हैं, लेकिन डिस्क को बहु-सूत्र नहीं पढ़ता है।


Array.Equals का उपयोग करना एक बुरा विचार है क्योंकि यह पूरे सरणी की तुलना करता है। यह संभावना है, कम से कम एक ब्लॉक रीड पूरे सरणी को नहीं भरेगा।
डग क्लटर

पूरे सरणी की तुलना एक बुरे विचार से क्यों की जा रही है? क्यों एक ब्लॉक पढ़ा सरणी भर नहीं होगा? निश्चित रूप से एक अच्छा ट्यूनिंग बिंदु है, लेकिन यही कारण है कि आप आकारों के साथ खेलते हैं। एक अलग धागे में तुलना करने के लिए अतिरिक्त अंक।
रैंडमइन्सानो

जब आप एक बाइट सरणी को परिभाषित करते हैं, तो इसकी एक निश्चित लंबाई होगी। (जैसे - var बफर = नई बाइट [4096]) जब आप फ़ाइल से कोई ब्लॉक पढ़ते हैं, तो वह पूर्ण 4096 बाइट्स वापस कर सकती है या नहीं भी। उदाहरण के लिए, यदि फ़ाइल केवल 3000 बाइट्स लंबी है।
डग क्लटर

आह, अब मैं समझ गया! अच्छी खबर यह है कि पढ़ने के लिए सरणी में लोड बाइट्स की संख्या वापस आ जाएगी, इसलिए यदि सरणी को भरा नहीं जा सकता है, तो डेटा होगा। चूंकि हम समानता के लिए परीक्षण कर रहे हैं, पुराने बफर डेटा कोई फर्क नहीं पड़ेगा। डॉक्स: msdn.microsoft.com/en-us/library/9kstw824(v=vs.110).aspx
randomInsano

यह भी महत्वपूर्ण है, बराबरी () पद्धति का उपयोग करने की मेरी सिफारिश एक बुरा विचार है। मोनो में, वे स्मृति की तुलना करते हैं क्योंकि तत्व स्मृति में सन्निहित हैं। हालाँकि Microsoft इसे ओवरराइड नहीं करता है, इसके बजाय केवल एक संदर्भ तुलना करता है जो यहाँ हमेशा गलत होगा।
रैंडमइन्सानो

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 + = sizeof (long)) {if (BitConverter.ToInt64 (बफर 1, i)! = BitConNet .oInt64 (बफर 2, i)) { विवरण झूठा है; }} `` `
साइमन

2

मेरे प्रयोगों से पता चलता है कि यह निश्चित रूप से Stream.ReadByte () को कम समय में कॉल करने में मदद करता है, लेकिन पैकेज बाइट्स के लिए BitConverter का उपयोग करने से बाइट सरणी में बाइट्स की तुलना करने से बहुत फर्क नहीं पड़ता है।

तो यह "Math.Ceiling और पुनरावृत्तियों" को बदलने के लिए संभव है कि ऊपर दिए गए टिप्पणी में लूप सबसे सरल एक के साथ:

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

मुझे लगता है कि यह इस तथ्य के साथ करना है कि BitConverter.ToInt64 आपको तुलना करने से पहले थोड़ा काम करने की जरूरत है (तर्कों की जांच करें और फिर थोड़ा सा बदलाव करें) और दो सरणियों में 8 बाइट्स की तुलना में समान कार्य समाप्त होने की आवश्यकता है ।


1
Array.Equals सिस्टम में गहराई से जाता है, इसलिए यह C # में बाइट से जाने की तुलना में बहुत तेज़ होगा। मैं Microsoft के लिए बात नहीं कर सकता, लेकिन नीचे गहरी, मोनो सरणी समानता के लिए C के मेमकी () कमांड का उपयोग करता है। इससे ज्यादा तेज नहीं मिल सकता।
रैंडमइन्सानो

2
@RandomInsano का मतलब है कि आप मेम्कंप (), मेम्कपी नहीं ()
एसक्यूएल पुलिस

1

यदि फ़ाइलें बहुत बड़ी नहीं हैं, तो आप उपयोग कर सकते हैं:

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

यह केवल हैश की तुलना करने के लिए संभव होगा यदि हैश स्टोर करने के लिए उपयोगी हो।

(कुछ साफ करने के लिए कोड को संपादित किया।)


1

समान लंबाई वाली बड़ी फ़ाइलों पर एक और सुधार, क्रमिक रूप से फ़ाइलों को नहीं पढ़ना, बल्कि कम या ज्यादा यादृच्छिक ब्लॉकों की तुलना करना हो सकता है।

आप कई थ्रेड्स का उपयोग कर सकते हैं, फ़ाइल में विभिन्न पदों पर शुरू कर सकते हैं और आगे या पीछे की तुलना कर सकते हैं।

इस तरह आप फ़ाइल के मध्य / अंत में परिवर्तनों का पता लगा सकते हैं, तेजी से आप क्रमिक दृष्टिकोण का उपयोग करके वहां पहुंचेंगे।


1
क्या डिस्क थ्रैशिंग से यहाँ समस्याएँ पैदा होंगी?
रैंडमइन्सानो

भौतिक डिस्क ड्राइव हाँ, SSD इसे संभालती है।
TheLegendaryCopyCoder

1

यदि आपको केवल दो फ़ाइलों की तुलना करने की आवश्यकता है, तो मुझे लगता है कि सबसे तेज़ तरीका होगा (सी में, मुझे नहीं पता कि क्या यह .NET पर लागू है)

  1. दोनों फाइलें f1, f2 खोलें
  2. संबंधित फ़ाइल की लंबाई l1, l2 प्राप्त करें
  3. अगर l1! = l2 फाइलें अलग हैं; रुकें
  4. mmap () दोनों फाइलें
  5. एमएमएपी () एड फाइलों पर मेमकैंप () का उपयोग करें

OTOH, यदि आपको यह पता लगाने की आवश्यकता है कि क्या N फ़ाइलों के सेट में डुप्लिकेट फ़ाइलें हैं, तो N-way बिट-बाय-बिट तुलना से बचने के लिए सबसे तेज़ तरीका निस्संदेह हैश का उपयोग कर रहा है।


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

यहां कुछ उपयोगिता कार्य हैं जो आपको यह निर्धारित करने की अनुमति देते हैं कि क्या दो फ़ाइलों (या दो धाराओं) में समान डेटा है।

मैंने एक "तेज़" संस्करण प्रदान किया है जो बहु-थ्रेडेड है क्योंकि यह बाइट सरणियों की तुलना करता है (टास्क का उपयोग करके प्रत्येक थ्रेड को प्रत्येक फ़ाइल में क्या पढ़ा गया है) से भरा है।

जैसा कि अपेक्षित था, यह बहुत तेज है (लगभग 3x तेज) लेकिन यह अधिक सीपीयू (क्योंकि यह बहु थ्रेडेड है) और अधिक मेमोरी का उपभोग करता है (क्योंकि इसकी तुलना थ्रेड प्रति दो बाइट सरणी बफ़र्स की आवश्यकता होती है)।

    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 के साथ usings और शॉर्टकट के लिए एक ही फ़ाइल, फ़ाइल मौजूद नहीं है और अलग-अलग लंबाई:

/// <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 ...किन परिस्थितियों में दो एमडी 5 हैश अलग लंबाई होंगे?
मेंढक

-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)));
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.