35 votes

GZipStream et DeflateStream ne décompressent pas tous les octets

J'avais besoin d'un moyen de compresser des images en .net et j'ai donc cherché à utiliser la classe .net GZipStream (ou DeflateStream). Cependant, j'ai constaté que la décompression n'était pas toujours réussie, parfois les images se décompressaient bien et d'autres fois j'obtenais une erreur GDI+ indiquant que quelque chose était corrompu.

Après avoir étudié le problème, j'ai constaté que la décompression ne restituait pas tous les octets qu'elle avait compressés. Ainsi, si je compressais 2257974 octets, je ne récupérais parfois que 2257870 octets (chiffres réels).

Le plus drôle, c'est que parfois cela fonctionnait. J'ai donc créé cette petite méthode de test qui ne compresse que 10 octets et maintenant je ne récupère plus rien du tout.

J'ai essayé avec les deux classes de compression GZipStream et DeflateStream et j'ai vérifié mon code pour détecter d'éventuelles erreurs. J'ai même essayé de positionner le flux à 0 et de vider tous les flux, mais sans succès.

Voici mon code :

    public static void TestCompression()
    {
        byte[] test = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        byte[] result = Decompress(Compress(test));

        // This will fail, result.Length is 0
        Debug.Assert(result.Length == test.Length);
    }

    public static byte[] Compress(byte[] data)
    {
        var compressedStream = new MemoryStream();
        var zipStream = new GZipStream(compressedStream, CompressionMode.Compress);
        zipStream.Write(data, 0, data.Length);
        return compressedStream.ToArray();
    }

    public static byte[] Decompress(byte[] data)
    {
        var compressedStream = new MemoryStream(data);
        var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress);
        var resultStream = new MemoryStream();

        var buffer = new byte[4096];
        int read;

        while ((read = zipStream.Read(buffer, 0, buffer.Length)) > 0) {
            resultStream.Write(buffer, 0, read);
        }

        return resultStream.ToArray();
    }

49voto

Marc Gravell Points 482669

Vous devez Close() les ZipStream après avoir ajouté toutes les données que vous souhaitez compresser ; il conserve en interne une mémoire tampon d'octets non écrits (même si vous Flush() ) qui doit être rédigée.

Plus généralement, Stream es IDisposable Vous devez donc également être using chacun... (oui, je sais que MemoryStream ne perdra pas de données, mais si vous ne prenez pas cette habitude, elle vous mordra dans d'autres domaines. Stream s).

public static byte[] Compress(byte[] data)
{
    using (var compressedStream = new MemoryStream())
    using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress))
    {
        zipStream.Write(data, 0, data.Length);
        zipStream.Close();
        return compressedStream.ToArray();
    }
}

public static byte[] Decompress(byte[] data)
{
    using(var compressedStream = new MemoryStream(data))
    using(var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
    using (var resultStream = new MemoryStream())
    { ... }
}

[edit : mise à jour du commentaire] Re not using des choses comme MemoryStream - c'est toujours une question amusante, avec beaucoup de votes de part et d'autre de la barrière : mais en fin de compte...

(rhétorique - nous connaissons tous la réponse). MemoryStream implémenté ? s'agit-il d'un byte[] (propriété de .NET) ? s'agit-il d'un fichier mappé en mémoire (propriété du système d'exploitation) ?

La raison pour laquelle vous n'êtes pas using c'est parce que vous laissez la connaissance des détails de l'implémentation interne modifier la façon dont vous codez avec une API publique - c'est-à-dire que vous venez d'enfreindre les lois de l'encapsulation. L'API publique dit : Je suis IDisposable vous me "possédez" ; c'est donc à vous qu'il incombe de Dispose() quand vous aurez terminé.

3voto

Cheeso Points 87022

N'oubliez pas non plus que le DeflateStream de System.IO.Compression n'implémente pas l'algorithme deflate le plus efficace. Si vous le souhaitez, il existe une alternative aux BCL GZipStream et DeflateStream ; elle est implémentée dans une bibliothèque entièrement gérée basée sur le code zlib, qui est plus performante que le {Deflate,GZip}Stream intégré à cet égard. [Mais vous devez toujours fermer () le flux pour obtenir le flux complet].

Ces classes de flux sont livrées dans l'assemblage DotNetZlib, disponible dans la distribution DotNetZip à l'adresse suivante http://DotNetZip.codeplex.com/ .

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