2008-11-07 2 views
35

Ich brauchte eine Möglichkeit, Bilder in .net zu komprimieren, also habe ich in der Verwendung der .net GZipStream-Klasse (oder DeflateStream) untersucht. Ich fand jedoch, dass die Dekomprimierung nicht immer erfolgreich war, manchmal dekomprimierten die Bilder in Ordnung, und andere Male würde ich einen GDI + -Fehler bekommen, dass etwas beschädigt ist.GZipStream und DeflateStream werden nicht alle Bytes dekomprimieren

Nachdem ich das Problem untersucht hatte, stellte ich fest, dass die Dekomprimierung nicht alle komprimierten Bytes zurückgab. Also, wenn ich 2257974 Bytes komprimierte, würde ich manchmal nur 2257870 Bytes (reelle Zahlen) zurückbekommen.

Die lustigste Sache ist, dass es manchmal funktionieren würde. Also habe ich diese kleine Testmethode erstellt, die nur 10 Bytes komprimiert und jetzt bekomme ich überhaupt nichts zurück.

Ich habe es mit beiden Komprimierungsklassen GZipStream und DeflateStream versucht und ich habe meinen Code auf mögliche Fehler überprüft. Ich habe sogar versucht, den Stream auf 0 zu setzen und alle Streams zu löschen, aber ohne Glück.

Hier ist mein 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(); 
    } 
+0

Re deinen Kommentar - es kommt zu Puffern auf verschiedenen Ebenen; Wenn sie nicht alle geleert sind (in der richtigen Reihenfolge), erhalten Sie nicht alle Daten. –

+0

Beachten Sie, dass ich zum Beispiel nicht Close() auf dem MemoryStream selbst angerufen habe - also stimme ich teilweise zu ;-p –

+0

Ich werde ein Update dazu hinzufügen, auch ... –

Antwort

48

Sie müssen Close() die ZipStream nach Prüfung aller Daten hinzufügen, die Sie komprimieren möchten; Es speichert intern einen Puffer von ungeschriebenen Bytes (auch wenn Sie Flush()), die geschrieben werden müssen.

Allgemeiner Stream ist IDisposable, so dass Sie auch using jeder sein sollte ... (ja, ich weiß, dass MemoryStream wird nicht alle Daten verlieren, aber wenn Sie nicht in diese Gewohnheit bekommt, wird es beißen Sie mit anderen 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: aktualisiert re Kommentar] Re nicht using Dinge wie MemoryStream - das ist immer ein Spaß, mit vielen Stimmen auf beiden Seiten des Zauns: aber ultimatey ...

(rhetorisch - wir alle kennen die Antwort ...) Wie wird MemoryStream implementiert? ist es ein Byte [] (im Besitz von .NET)? ist es eine Memory-Mapped-Datei (im Besitz des Betriebssystems)?

Der Grund, warum Sie nicht using sind, ist, weil Sie Wissen über interne Implementierungsdetails ändern, wie Sie gegen eine öffentliche API codieren - d. H. Sie haben gerade die Gesetze der Kapselung gebrochen. Die öffentliche API sagt: Ich bin IDisposable; du besitzt mich; Daher ist es deine Aufgabe, mich zu Dispose() wenn du durch bist.

+0

Wow, es hat wie ein Charme funktioniert. Es ist interessant, weil ich nicht dachte, dass Close() für den internen Speicher notwendig ist, da keine Windows-Ressource beteiligt ist (das gleiche gilt für den Verwendungsblock - es war einfach sauberer ohne) –

+0

Close() geht es nicht darum, zu befreien eine Windows-Ressource hier. GZip benötigt eine Fußzeile am Ende der Daten, und Close() teilt GZipStream mit, dass Sie mit dem Schreiben der Daten fertig sind und die Fußzeile ausschreiben sollte. – stevemegson

+0

Sie haben vielleicht Recht, wenn Sie Implementierungsdetails definieren, wie ich den richtigen Code definiere. Aber in letzter Zeit hat Microsoft das Entsorgungsmuster (auch für keine Einwegobjekte) zu sehr implementiert –

3

Beachten Sie auch, dass DeflateStream in System.IO.Compression nicht den effizientesten Deflate-Algorithmus implementiert. Wenn Sie möchten, gibt es eine Alternative zu BCL GZipStream und DeflateStream; Es ist in einer vollständig verwalteten Bibliothek implementiert, die auf zlib-Code basiert und in dieser Hinsicht eine bessere Leistung bietet als der integrierte {Deflate, GZip} -Stream. [Sie müssen jedoch den Stream schließen, um den vollständigen Bytestream zu erhalten. ]

Diese Stream-Klassen werden in der DotNetZlib-Baugruppe geliefert, die in der DotNetZip-Verteilung unter http://DotNetZip.codeplex.com/ verfügbar ist.

+1

Sie werden erfreut sein zu hören, dass .NET 4.5 den zlib-Algorithmus jetzt in den BCL integriert hat (mit Abwärtskompatibilität für vorhandene komprimierte Daten). Sehen Sie hier für weitere Informationen: http://msdn.microsoft.com/en-us/magazine/jj133817.aspx – pattermeister

+1

Terrific! Es hat zu lange gedauert, aber ich bin froh, dass es endlich da ist! – Cheeso

Verwandte Themen