2010-09-15 18 views
35

Ho riscontrato un problema con questa funzione di test in cui prendo una stringa in memoria, la comprime e la decomprimo. La compressione funziona alla grande, ma non riesco a far funzionare la decompressione.Come utilizzare GZipStream con System.IO.MemoryStream?

//Compress 
System.IO.MemoryStream outStream = new System.IO.MemoryStream();     
GZipStream tinyStream = new GZipStream(outStream, CompressionMode.Compress); 
mStream.Position = 0; 
mStream.CopyTo(tinyStream); 

//Decompress  
outStream.Position = 0; 
GZipStream bigStream = new GZipStream(outStream, CompressionMode.Decompress); 
System.IO.MemoryStream bigStreamOut = new System.IO.MemoryStream(); 
bigStream.CopyTo(bigStreamOut); 

//Results: 
//bigStreamOut.Length == 0 
//outStream.Position == the end of the stream. 

Credo che bigStream fuori dovrebbe almeno avere i dati in essa contenuti, soprattutto se il mio flusso di fonti (outStream) è in corso la lettura. questo è un bug MSFT o il mio?

risposta

84

Ciò che accade nel codice è che continui ad aprire flussi, ma non li chiudi mai.

  • Nella riga 2, si crea un GZipStream. Questo stream non scriverà nulla sullo stream sottostante fino a quando non sentirà che è il momento giusto. Puoi dirlo chiudendolo.

  • Tuttavia, se lo si chiude, verrà chiuso anche il flusso sottostante (outStream). Pertanto non è possibile utilizzare mStream.Position = 0 su di esso.

È necessario utilizzare sempre using per garantire che tutti i flussi vengano chiusi. Ecco una variante del tuo codice che funziona.

var inputString = "“ ... ”"; 
byte[] compressed; 
string output; 

using (var outStream = new MemoryStream()) 
{ 
    using (var tinyStream = new GZipStream(outStream, CompressionMode.Compress)) 
    using (var mStream = new MemoryStream(Encoding.UTF8.GetBytes(inputString))) 
     mStream.CopyTo(tinyStream); 

    compressed = outStream.ToArray(); 
} 

// “compressed” now contains the compressed string. 
// Also, all the streams are closed and the above is a self-contained operation. 

using (var inStream = new MemoryStream(compressed)) 
using (var bigStream = new GZipStream(inStream, CompressionMode.Decompress)) 
using (var bigStreamOut = new MemoryStream()) 
{ 
    bigStream.CopyTo(bigStreamOut); 
    output = Encoding.UTF8.GetString(bigStreamOut.ToArray()); 
} 

// “output” now contains the uncompressed string. 
Console.WriteLine(output); 
+7

+1 buona risposta Timwi. Solo per aggiungere a questo, GZip ha un buffer interno dei dati che deve fare per comprimere. Non può sapere che ha finito di ricevere dati finché non lo si chiude e quindi non sputa gli ultimi byte e la decompressione del flusso parziale fallisce. – MerickOWA

+0

Penso che siamo su .NET 3.5 (lavorando con Unity), quindi. Copio non esiste ancora. Guardando altrove su SO per come copiare da uno stream all'altro: http://stackoverflow.com/questions/230128/best-way-to-copy-between-two-stream-stances – Almo

+0

Grazie per questo, sono stato avere problemi a capire esattamente come organizzare i flussi per ottenere l'output corretto in entrambe le direzioni – MikeT

31

Questo è un problema noto: http://blogs.msdn.com/b/bclteam/archive/2006/05/10/592551.aspx

ho cambiato il codice un po 'così questo funziona:

var mStream = new MemoryStream(new byte[100]); 
var outStream = new System.IO.MemoryStream(); 

using (var tinyStream = new GZipStream(outStream, CompressionMode.Compress)) 
{ 
    mStream.CopyTo(tinyStream);   
} 

byte[] bb = outStream.ToArray(); 

//Decompress     
var bigStream = new GZipStream(new MemoryStream(bb), CompressionMode.Decompress); 
var bigStreamOut = new System.IO.MemoryStream(); 
bigStream.CopyTo(bigStreamOut); 
4

Un'altra implementazione, in VB.NET:

Imports System.Runtime.CompilerServices 
Imports System.IO 
Imports System.IO.Compression 

Public Module Compressor 

    <Extension()> _ 
    Function CompressASCII(str As String) As Byte() 

     Dim bytes As Byte() = Encoding.ASCII.GetBytes(str) 

     Using ms As New MemoryStream 

      Using gzStream As New GZipStream(ms, CompressionMode.Compress) 

       gzStream.Write(bytes, 0, bytes.Length) 

      End Using 

      Return ms.ToArray 

     End Using 

    End Function 

    <Extension()> _ 
    Function DecompressASCII(compressedString As Byte()) As String 

     Using ms As New MemoryStream(compressedString) 

      Using gzStream As New GZipStream(ms, CompressionMode.Decompress) 

       Using sr As New StreamReader(gzStream, Encoding.ASCII) 

        Return sr.ReadToEnd 

       End Using 

      End Using 

     End Using 

    End Function 

    Sub TestCompression() 

     Dim input As String = "fh3o047gh" 

     Dim compressed As Byte() = input.CompressASCII() 

     Dim decompressed As String = compressed.DecompressASCII() 

     If input <> decompressed Then 
      Throw New ApplicationException("failure!") 
     End If 

    End Sub 

End Module 
1
public static byte[] compress(byte[] data) 
    { 
     using (MemoryStream outStream = new MemoryStream()) 
     { 
      using (GZipStream gzipStream = new GZipStream(outStream, CompressionMode.Compress)) 
      using (MemoryStream srcStream = new MemoryStream(data)) 
       srcStream.CopyTo(gzipStream); 
      return outStream.ToArray(); 
     } 
    } 

    public static byte[] decompress(byte[] compressed) 
    { 
     using (MemoryStream inStream = new MemoryStream(compressed)) 
     using (GZipStream gzipStream = new GZipStream(inStream, CompressionMode.Decompress)) 
     using (MemoryStream outStream = new MemoryStream()) 
     { 
      gzipStream.CopyTo(outStream); 
      return outStream.ToArray(); 
     } 
    } 
1

Se si sta tentando di g per usare MemoryStream (ad es. passarlo in un'altra funzione) ma ricevendo l'eccezione "Impossibile accedere a un flusso chiuso". poi c'è un altro costruttore GZipStream che puoi usare che ti aiuterà.

Passando un parametro true al parametro leaveOpen, è possibile impostare GZipStream in modo che lasci il flusso aperto dopo aver eliminato se stesso, per impostazione predefinita chiude il flusso di destinazione (cosa che non mi aspettavo). https://msdn.microsoft.com/en-us/library/27ck2z1y(v=vs.110).aspx

using (FileStream fs = File.OpenRead(f)) 
using (var compressed = new MemoryStream()) 
{ 
    //Instruct GZipStream to leave the stream open after performing the compression. 
    using (var gzipstream = new GZipStream(compressed, CompressionLevel.Optimal, true)) 
     fs.CopyTo(gzipstream); 

    //Do something with the memorystream 
    compressed.Seek(0, SeekOrigin.Begin); 
    MyFunction(compressed); 
} 
2

Il modo per comprimere e decomprimere, da e verso un MemoryStream è:

public static Stream Compress(
    Stream decompressed, 
    CompressionLevel compressionLevel = CompressionLevel.Fastest) 
{ 
    var compressed = new MemoryStream(); 
    using (var zip = new GZipStream(compressed, compressionLevel, true)) 
    { 
     decompressed.CopyTo(zip); 
    } 

    compressed.Seek(0, SeekOrigin.Begin); 
    return compressed; 
} 

public static Stream Decompress(Stream compressed) 
{ 
    var decompressed = new MemoryStream(); 
    using (var zip = new GZipStream(compressed, CompressionMode.Decompress, true)) 
    { 
     zip.CopyTo(decompressed); 
    } 

    decompressed.Seek(0, SeekOrigin.Begin); 
    return decompressed; 
} 

Questo lascia lo stream compresso/decompresso aperto e come tale utilizzabile dopo averlo creato.