144 votes

Comment comparer rapidement deux fichiers à l'aide de .NET ?

Approches typiques recommande de lire le binaire via FileStream et de le comparer octet par octet.

  • Une comparaison de somme de contrôle telle que le CRC serait-elle plus rapide ?
  • Existe-t-il des bibliothèques .NET qui peuvent générer une somme de contrôle pour un fichier ?

0 votes

143voto

chsh Points 913

La méthode la plus lente possible consiste à comparer deux fichiers octet par octet. La méthode la plus rapide que j'ai pu trouver est une comparaison similaire, mais au lieu d'un octet à la fois, vous utiliseriez un tableau d'octets de taille Int64, puis vous compareriez les nombres résultants.

Voici ce que j'ai trouvé :

    const int BYTES_TO_READ = sizeof(Int64);

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

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

Lors de mes tests, j'ai pu constater que cette méthode était plus performante qu'un simple scénario ReadByte() dans un rapport de presque 3:1. Sur une moyenne de 1000 exécutions, j'ai obtenu cette méthode à 1063 ms, et la méthode ci-dessous (comparaison directe octet par octet) à 3031 ms. Le hachage est toujours revenu à moins d'une seconde avec une moyenne de 865 ms. Ces tests ont été effectués avec un fichier vidéo d'environ 100 Mo.

Voici les méthodes ReadByte et hashing que j'ai utilisées, à titre de comparaison :

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

        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 votes

Vous m'avez rendu la vie plus facile. Je vous remercie.

2 votes

@anindis : Pour être complet, vous pouvez lire les deux. Réponse de @Lars y La réponse de @RandomInsano . Heureux que cela ait aidé tant d'années après ! :)

1 votes

El FilesAreEqual_Hash devrait avoir un using sur les deux flux de fichiers aussi comme le ReadByte sinon il s'accrochera aux deux fichiers.

127voto

Reed Copsey Points 315315

Une comparaison de somme de contrôle sera très probablement plus lente qu'une comparaison octet par octet.

Pour générer une somme de contrôle, vous devez charger chaque octet du fichier et effectuer un traitement sur celui-ci. Vous devrez ensuite faire de même avec le second fichier. Le traitement sera presque certainement plus lent que la vérification par comparaison.

Quant à la génération d'une somme de contrôle, vous pouvez le faire facilement avec les classes de cryptographie. Voici un bref exemple de génération d'une somme de contrôle MD5 avec C#.

Cependant, une somme de contrôle peut être plus rapide et avoir plus de sens si vous pouvez précalculer la somme de contrôle du cas "test" ou "de base". Si vous avez un fichier existant et que vous vérifiez si un nouveau fichier est identique à l'existant, le précalcul de la somme de contrôle sur votre fichier "existant" signifierait que vous n'auriez besoin de faire le DiskIO qu'une seule fois, sur le nouveau fichier. Cela serait probablement plus rapide qu'une comparaison octet par octet.

31 votes

Veillez à prendre en compte l'emplacement de vos fichiers. Si vous comparez des fichiers locaux à une copie de sauvegarde située à l'autre bout du monde (ou sur un réseau dont la bande passante est mauvaise), il est préférable de commencer par hacher les données et d'envoyer une somme de contrôle sur le réseau plutôt que d'envoyer un flux d'octets à comparer.

0 votes

@ReedCopsey : J'ai un problème similaire, puisque j'ai besoin de stocker des fichiers d'entrée/sortie produits par plusieurs élaborations qui sont censés contenir beaucoup de duplications. J'ai pensé à utiliser un hachage précalculé, mais pensez-vous que je peux raisonnablement supposer que si 2 hachages (par exemple MD5) sont égaux, les 2 fichiers sont égaux et éviter une comparaison supplémentaire octet par octet ? Pour autant que je sache, les collisions MD5/SHA1 etc. sont vraiment peu probables...

1 votes

@digEmAll Le risque de collision est faible. Vous pouvez cependant toujours effectuer un hachage plus fort, c'est-à-dire utiliser SHA256 au lieu de SHA1, ce qui réduira encore plus le risque de collision.

33voto

dtb Points 104373

En plus de Reed Copsey La réponse de la Commission :

  • Le pire cas est celui où les deux fichiers sont identiques. Dans ce cas, il est préférable de comparer les fichiers octet par octet.

  • Si les deux fichiers ne sont pas identiques, vous pouvez accélérer un peu les choses en détectant plus tôt qu'ils ne sont pas identiques.

Par exemple, si les deux fichiers sont de longueur différente, vous savez qu'ils ne peuvent pas être identiques, et vous n'avez même pas besoin de comparer leur contenu réel.

10 votes

Pour être complet : l'autre grand gain est de s'arrêter dès que les octets à 1 position sont différents.

6 votes

@Henk : Je pensais que c'était trop évident :-)

1 votes

Bonne idée d'ajouter ça. C'était évident pour moi, donc je ne l'ai pas inclus, mais c'est bien de le mentionner.

18voto

Lars Points 342

Il devient encore plus rapide si vous ne lisez pas par petits morceaux de 8 octets mais si vous faites une boucle autour, en lisant un plus grand morceau. J'ai réduit le temps moyen de comparaison à 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 = 2048 * 2;
        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 votes

En général, le contrôle count1 != count2 n'est pas correct. Stream.Read() peut retourner moins que le nombre que vous avez fourni, pour diverses raisons.

14voto

Guffa Points 308133

La seule chose qui pourrait rendre une comparaison de somme de contrôle légèrement plus rapide qu'une comparaison octet par octet est le fait que vous lisez un fichier à la fois, ce qui réduit quelque peu le temps de recherche de la tête du disque. Ce léger gain peut cependant très bien être absorbé par le temps supplémentaire de calcul du hachage.

En outre, la comparaison des sommes de contrôle n'a bien sûr aucune chance d'être plus rapide que si les fichiers sont identiques. S'ils ne le sont pas, une comparaison octet par octet s'arrêterait à la première différence, ce qui la rendrait beaucoup plus rapide.

Vous devez aussi considérer qu'une comparaison de code de hachage vous indique seulement que c'est très probable que les fichiers sont identiques. Pour être sûr à 100%, vous devez faire une comparaison octet par octet.

Si le code de hachage, par exemple, est de 32 bits, vous êtes certain à 99,99999998 % que les fichiers sont identiques si les codes de hachage correspondent. C'est proche de 100 %, mais si vous avez vraiment besoin d'une certitude à 100 %, ce n'est pas le cas.

0 votes

Utilisez un hachage plus grand et vous pouvez faire en sorte que les chances d'un faux positif soient bien inférieures aux chances que l'ordinateur se soit trompé en effectuant le test.

0 votes

Je ne suis pas d'accord sur le temps de hachage par rapport au temps de recherche. Vous pouvez faire un lot de calculs pendant une seule recherche de tête. S'il y a de fortes chances que les fichiers correspondent, j'utiliserais un hachage avec beaucoup de bits. S'il y a une chance raisonnable de correspondance, je les comparerais bloc par bloc, par exemple des blocs de 1 Mo. (Choisissez une taille de bloc que 4k divise de manière égale pour vous assurer de ne jamais diviser les secteurs).

1 votes

Pour expliquer le chiffre de 99,99999998% de @Guffa, il provient du calcul 1 - (1 / (2^32)) qui est la probabilité qu'un fichier unique ait un hachage de 32 bits donné. La probabilité que deux fichiers différents aient le même hachage est la même, car le premier fichier fournit la valeur de hachage "donnée", et il suffit de considérer si l'autre fichier correspond ou non à cette valeur. Les chances avec un hachage de 64 et 128 bits diminuent à 99,999999999999999994% et 99,99999999999999999999999999999999999999999999997% (respectivement), comme si cela avait de l'importance avec des nombres aussi insondables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X