2008-11-07 13 views
35

Necesitaba una forma de comprimir imágenes en .net, así que investigué usando la clase .net GZipStream (o DeflateStream). Sin embargo, descubrí que la descompresión no siempre era exitosa, a veces las imágenes se descomprimían bien y otras veces obtenía un error GDI + de que algo estaba dañado.GZipStream y DeflateStream no descomprimirán todos los bytes

Después de investigar el problema, encontré que la descompresión no devolvía todos los bytes que comprimía. Entonces, si comprimía 2257974 bytes, a veces obtengo solo 2257870 bytes (números reales).

Lo más divertido es que a veces funcionaría. Así que creé este pequeño método de prueba que comprime solo 10 bytes y ahora no recupero nada.

Lo probé con las dos clases de compresión GZipStream y DeflateStream y revisé mi código para ver si había posibles errores. Incluso intenté posicionar la transmisión en 0 y lavar todas las transmisiones, pero sin suerte.

Aquí está mi código:

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 su comentario - se reduce a los almacenamientos intermedios en diferentes niveles; si no están todos vacíos (en el orden correcto), entonces no se obtienen todos los datos. –

+0

Tenga en cuenta, por ejemplo, que no me molesté en llamar a Close() en el mismo MemoryStream, así que estoy parcialmente de acuerdo ;-p –

+0

Agregaré una actualización sobre esto, también ... –

Respuesta

48

Es necesario Close() la ZipStream después de añadir todos los datos que desea comprimir; retiene un búfer de bytes no escritos internamente (incluso si tiene Flush()) que necesita ser escrito.

De manera más general, es StreamIDisposable, lo que también debe ser using cada ... (sí, sé que MemoryStream no va a perder ningún dato, pero si usted no recibe en este hábito, se morderlo con otros 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()) 
    { ... } 
} 

[editar: actualizado re comentario] Re no using cosas como MemoryStream - esto es siempre una diversión, con una gran cantidad de votos en cada lado de la valla, pero ultimatey ...

(Retórica: todos sabemos la respuesta ... ¿Cómo se implementa MemoryStream? ¿es un byte [] (propiedad de .NET)? ¿es un archivo mapeado en memoria (propiedad del sistema operativo)?

La razón por la que no está using es porque está dejando que el conocimiento de los detalles de implementación interna cambie la forma en que codifica en contra de una API pública, es decir, acaba de incumplir las leyes de encapsulación. La API pública dice: soy IDisposable; usted "me posee" a mí; por lo tanto, es su trabajo a Dispose() cuando haya terminado.

+0

Guau, funcionó como un encanto. Es interesante porque no pensé que Close() es necesario para la memoria interna ya que no hay recurso de Windows involucrado (lo mismo vale para el bloque que usa - fue más limpio sin ellos) –

+0

Cerrar() no se trata de liberar un recurso de Windows aquí. GZip requiere un pie de página al final de los datos, y Close() le dice a GZipStream que ha terminado de escribir datos y que debe escribir el pie de página. – stevemegson

+0

Es posible que tenga razón al permitir que los detalles de implementación definan cómo código correcto. Pero últimamente Microsoft ha estado implementando en gran medida el patrón de eliminación (incluso en ninguno de los objetos desechables) –

3

Además, tenga en cuenta que DeflateStream en System.IO.Compression no implementa el algoritmo de desinflado más eficiente. Si lo desea, existe una alternativa al BCL GZipStream y DeflateStream; se implementa en una biblioteca totalmente administrada basada en el código zlib, que funciona mejor que el flujo integrado {Deflate, GZip} a este respecto. [Pero aún necesita Cerrar() la transmisión para obtener la corriente de detección completa. ]

Estas clases de flujo se envían en el ensamblaje DotNetZlib, disponible en la distribución DotNetZip al http://DotNetZip.codeplex.com/.

+1

Estará satisfecho escuchar que .NET 4.5 se ha entretejido en el algoritmo zlib en el BCL ahora (con compatibilidad hacia atrás para datos comprimidos existentes). Consulte aquí para obtener más información: http://msdn.microsoft.com/en-us/magazine/jj133817.aspx – pattermeister

+1

¡Terrific! Me llevó demasiado tiempo, ¡pero me alegro de que finalmente esté allí! – Cheeso

Cuestiones relacionadas