views:

593

answers:

2

I'm having trouble reading a compressed (deflated) data file using C# .NET DeflateStream(..., CompressionMode.Decompress). The file was written earlier using DeflateStream(..., CompressionMode.Compress), and it seems to be just fine (I can even decompress it using a Java program).

However, the first Read() call on the input stream to decompress/inflate the compressed data returns a length of zero (end of file).

Here's the main driver, which is used for both compression and decompression:

public void Main(...)
{
    Stream  inp;
    Stream  outp;
    bool    compr;  

    ...
    inp = new FileStream(inName, FileMode.Open, FileAccess.Read);
    outp = new FileStream(outName, FileMode.Create, FileAccess.Write);  

    if (compr)
        Compress(inp, outp);
    else
        Decompress(inp, outp);  

    inp.Close();
    outp.Close();
}

Here's the basic code for decompression, which is what is failing:

public long Decompress(Stream inp, Stream outp)
{
    byte[]  buf = new byte[BUF_SIZE];
    long    nBytes = 0;  

    // Decompress the contents of the input file
    inp = new DeflateStream(inp, CompressionMode.Decompress);  

    for (;;)
    {
        int   len;  

        // Read a data block from the input stream
        len = inp.Read(buf, 0, buf.Length);    //<<FAILS
        if (len <= 0)
            break;  

        // Write the data block to the decompressed output stream
        outp.Write(buf, 0, len);
        nBytes += len;
    }  

    // Done
    outp.Flush();
    return nBytes;
}

The call marked FAILS always returns zero. Why? I know it's got to be something simple, but I'm just not seeing it.

Here's the basic code for compression, which works just fine, and is almost exactly the same as the decompression method with the names swapped:

public long Compress(Stream inp, Stream outp)
{
    byte[]  buf = new byte[BUF_SIZE];
    long    nBytes = 0;  

    // Compress the contents of the input file
    outp = new DeflateStream(outp, CompressionMode.Compress);  

    for (;;)
    {
        int   len;  

        // Read a data block from the input stream
        len = inp.Read(buf, 0, buf.Length);
        if (len <= 0)
            break;  

        // Write the data block to the compressed output stream
        outp.Write(buf, 0, len);
        nBytes += len;
    }  

    // Done
    outp.Flush();
    return nBytes;
}

Solved

After seeing the correct solution, the constructor statement should be changed to:

inp = new DeflateStream(inp, CompressionMode.Decompress, true);

which keeps the underlying input stream open, and the following line needs to be added following the inp.Flush() call:

inp.Close();

The Close() calls forces the deflater stream to flush its internal buffers. The true flag prevents it from closing the underlying stream, which is closed later in Main(). The same changes should also be made to the Compress() method.

A: 

I had the same problem with GZipStream, Since we had the original length stored I had to re-write the code to only read the number of bytes expected in the original file.

Hopefully I'm about to learn that there was a better answer (fingers crossed).

csharptest.net
+1  A: 

In your decompress method, are reassigning inp to a new Stream (a deflate stream). You never close that Deflate stream, but you do close the underlying file stream in Main(). A similar thing is going on in the compress method.

I think that the problem is that the underlying file stream is being closed before the deflate stream's finalizers are automatically closing them.

I added 1 line of code to your Decompress and Compress methods: inp.Close() // to the Decompressmehtod

outp.Close() // to the compress method.

a better practice would be to enclose the streams in a using clause.

Here's an alternative way to write your Decompress method (I tested, and it works)


    public static long Decompress(Stream inp, Stream outp)
    {
     byte[]  buf = new byte[BUF_SIZE];
     long    nBytes = 0;  

     // Decompress the contents of the input file
     using (inp = new DeflateStream(inp, CompressionMode.Decompress))
     {
      int len;
      while ((len = inp.Read(buf, 0, buf.Length)) > 0)
      {
       // Write the data block to the decompressed output stream
       outp.Write(buf, 0, len);
       nBytes += len;
      }  
     }
     // Done
     return nBytes;
    }
    
JMarsch
@LoadMaster: BTW: When I ran your code, I didn't get the same behavior you described, but it didn't work, either. In my case, the decompress appeared to be successfuly, but if I compared the decompressed file with the original, they didn't match. The decompressed file was smaller than the original -- missing bytes. The change where we call close() on the DeflateStream fixed that problem as well. I suspect that the difference in behavior might just have to do with the size of my test file vs yours -- maybe it dropped the last N bytes, and my file was bigger (mine was 364,391 bytes).
JMarsch
Doh. Of course, it says there right in the docs that you have to close the deflater stream to ensure that all the internal buffers are flushed properly. I should be calling `Close()` on the compressors/decompressor streams in the `Decompress()` and `Compress()` methods, since they control the compressor/decompressor streams directly.Alternatively, I could open the `DeflaterStream` with the `keepOpen` flag true to keep the underlying input stream open. I'd still close the deflater stream, but then I'd also close the underlying file stream afterwards. I tried this, and it works.
Loadmaster