Dave
Dave

Reputation: 8461

Compressing with GZipStream

I am trying to understand why my code doesn't execute as desired. It creates a GZipStream, and then saves the object as compressed file on my hard drive, but the saved file is always 0 bytes.

Now I know how to save a file using GZipStream, but, my question is not how to do it. My question is purely why does this code save 0 bytes (or why FileStream works and memory doesn't).

private void BegingCompression()
{
    var bytes = File.ReadAllBytes(this.fileName);
    using (MemoryStream ms = new MemoryStream(bytes))
    {
        ms.ReadByte();
        using (FileStream fs =new FileStream(this.newFileName, FileMode.CreateNew))
        using (GZipStream zipStream = new GZipStream(ms, CompressionMode.Compress, false))
        {
            zipStream.Write(bytes, 0, bytes.Length);
        }
    }
}

In regards to the source code, this.fileName = c:\Audio.wav and the newFileName is c:\Audio.wav.gz (but have also tried c:\audio.gz)

Upvotes: 19

Views: 63722

Answers (4)

isxaker
isxaker

Reputation: 9456

I use this class for compressing/decompressing:

internal class GZipProcessor : IZipProcessor
{

    public 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 byte[] Decompress(byte[] data)
    {
        using (var compressedStream = new MemoryStream(data))
        {
            using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
            {
                using (var resultStream = new MemoryStream())
                {
                    zipStream.CopyTo(resultStream);
                    return resultStream.ToArray();
                }
            }
        }
    }

}

And how i can use it:

public void Compress(string inputPath, string outputPath)
{
    byte[] originalBytes = File.ReadAllBytes(inputPath);
    byte[] zippedBytes = base.ZipProcessor.Compress(originalBytes);
    File.WriteAllBytes(outputPath, zippedBytes);
}

public void Decompress(string inputPath, string outputPath)
{
    byte[] zippedBytes = File.ReadAllBytes(inputPath);
    byte[] originalBytes = base.ZipProcessor.Decompress(zippedBytes);
    File.WriteAllBytes(outputPath, originalBytes);
}

also i have a github repository with more complex code

Upvotes: 14

Erik
Erik

Reputation: 12858

When you use GzipStream or DeflateStream from the System.IO.Compression namespace, the Stream you supply in the constructor will be written to for compression and read from in decompression.

Since you are trying to compress the data here, using the MemoryStream is incorrect as you are not trying to compress to it, but rather use it as a data source. So your MemoryStream should be the input Stream and the FileStream is your output.

I highly recommend you using MemoryStream as a data source over the raw byte[] because Stream has a lot more versatility and application (FileStream, NetworkStream, CryptoStream, etc.)

Here are some examples using the async/await pattern:

public static async Task CompressToFileAsync(byte[] buffer, 
                                             string outputFile)
{
  using (var inputStream = new MemoryStream(buffer))
    await CompressToFileAsync(inputStream, outputFile);
}

public static async Task CompressToFileAsync(Stream inputStream, 
                                             string outputFile)
{
  using (var outputStream = File.Create(outputFile))
  using (var gzip = new GZipStream(outputStream, CompressionMode.Compress))
  {
    await inputStream.CopyToAsync(gzip);
    gzip.Close();
  }
}

public static async Task<MemoryStream> DecompressFromFileAsync(string inputFile)
{
  var outputStream = new MemoryStream();

  using (var inputStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read))
  using (var gzip = new GZipStream(inputStream, CompressionMode.Decompress))
  {
    await gzip.CopyToAsync(outputStream);

    gzip.Close();
    inputStream.Close();

    // After writing to the MemoryStream, the position will be the size
    // of the decompressed file, we should reset it back to zero before returning.
    outputStream.Position = 0;
    return outputStream;
  }
}

NOTE: Always call GzipStream.Close() before you close the input or output Stream. It does some final buffer flushing when closing/disposed and if the input or output is closed first it will throw an exception when it tries to do so. (This also applies to DeflateStream)

Upvotes: 14

Richard Schneider
Richard Schneider

Reputation: 35477

  • You do not need a MemoryStream because bytes already has the data to compress.
  • ms.ReadByte() should not be used.
  • When creating the zipStream the output file stream should be used.

Try this:

var bytes = File.ReadAllBytes(this.fileName);
using (FileStream fs =new FileStream(this.newFileName, FileMode.CreateNew))
using (GZipStream zipStream = new GZipStream(fs, CompressionMode.Compress, false))
{
     zipStream.Write(bytes, 0, bytes.Length);
}

EDIT

The original code creates a zero length file because you do not write to the file stream.

Upvotes: 19

SergeyS
SergeyS

Reputation: 3553

why FileStream works and memory doesn't

Because backing store of MemoryStream is your RAM memory, not hard drive. So when you passing MemoryStream object to GZipStream constructor, you gzip will be just in RAM.

With this code:

using (FileStream fs =new FileStream(this.newFileName, FileMode.CreateNew))

you are creating new FileStream, but you did not use it. To store gzip using this FIleStream, you need to pass it to GZipStream constructor, to make it backing store to be hard drive.

Upvotes: 3

Related Questions