Skip to main content

CSharp methods for compressing and decompressing files asynchronously. Taken from C# 6.0 Cookbook by Jay Hilyard and Stephen Teilhet.

public static async void TestCompressNewFileAsync()
{
    byte[] data = new byte[10000000];
    for (int i = 0; i < 10000000; i++)
        data[i] = (byte)i;


    string normalFilePath = Path.GetTempPath() + "NewNormalFile.txt";
    string deflateCompressedFilePath = Path.GetTempPath() + "NewCompressedFile.txt";
    string deflateDecompressedFilePath = Path.GetTempPath() + "NewDecompressedFile.txt";
    string gzCompressedFilePath = Path.GetTempPath() + "NewGZCompressedFile.txt";
    string gzDecompressedFilePath = Path.GetTempPath() + "NewGZDecompressedFile.txt";

    Console.WriteLine($"Base file: {normalFilePath}");
    Console.WriteLine($"Deflate compressed file: {deflateCompressedFilePath}");
    Console.WriteLine($"Defalte decompressed file: {deflateDecompressedFilePath}");
    Console.WriteLine($"GZip compressed file: {gzCompressedFilePath}");
    Console.WriteLine($"GZip decompressed file: {gzDecompressedFilePath}");

    using (FileStream fs =
        new FileStream(normalFilePath,
            FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None,
            4096, useAsync:true))
    {
        await fs.WriteAsync(data, 0, data.Length);
    }

    await CompressFileAsync(normalFilePath, deflateCompressedFilePath,
        CompressionType.Deflate);

    await DecompressFileAsync(deflateCompressedFilePath, deflateDecompressedFilePath,
        CompressionType.Deflate);

    await CompressFileAsync(normalFilePath, gzCompressedFilePath,
        CompressionType.GZip);

    await DecompressFileAsync(gzCompressedFilePath, gzDecompressedFilePath,
        CompressionType.GZip);

    //Normal file size == 10,000,000 bytes
    //GZipped file size == 84,362
    //Deflated file size == 42,145
    //Pre .NET 4.5 GZipped file size == 155,204
    //Pre .NET 4.5 Deflated file size == 155,168

    // 36 bytes are related to the GZip CRC
}

/// <summary>
/// Compress the source file to the destination file.
/// This is done in 1MB chunks to not overwhelm the memory usage.
/// </summary>
/// <param name="sourceFile">the uncompressed file</param>
/// <param name="destinationFile">the compressed file</param>
/// <param name="compressionType">the type of compression to use</param>
public static async Task CompressFileAsync(string sourceFile,
                                string destinationFile,
                                CompressionType compressionType)
{
    if (string.IsNullOrWhiteSpace(sourceFile))
        throw new ArgumentNullException(nameof(sourceFile));

    if (string.IsNullOrWhiteSpace(destinationFile))
        throw new ArgumentNullException(nameof(destinationFile));

    FileStream streamSource = null;
    FileStream streamDestination = null;
    Stream streamCompressed = null;

    int bufferSize = 4096;
    using (streamSource = new FileStream(sourceFile,
            FileMode.OpenOrCreate, FileAccess.Read, FileShare.None,
            bufferSize, useAsync: true))
    {
        using (streamDestination = new FileStream(destinationFile,
            FileMode.OpenOrCreate, FileAccess.Write, FileShare.None,
            bufferSize, useAsync: true))
        {
            // read 1MB chunks and compress them
            long fileLength = streamSource.Length;

            // write out the fileLength size
            byte[] size = BitConverter.GetBytes(fileLength);
            await streamDestination.WriteAsync(size, 0, size.Length);

            long chunkSize = 1048576; // 1MB
            while (fileLength > 0)
            {
                // read the chunk
                byte[] data = new byte[chunkSize];
                await streamSource.ReadAsync(data, 0, data.Length);

                // compress the chunk
                MemoryStream compressedDataStream =
                    new MemoryStream();

                if (compressionType == CompressionType.Deflate)
                    streamCompressed =
                        new DeflateStream(compressedDataStream,
                            CompressionMode.Compress);
                else
                    streamCompressed =
                        new GZipStream(compressedDataStream,
                            CompressionMode.Compress);

                using (streamCompressed)
                {
                    // write the chunk in the compressed stream
                    await streamCompressed.WriteAsync(data, 0, data.Length);
                    await streamCompressed.FlushAsync();
                }
                // get the bytes for the compressed chunk
                byte[] compressedData =
                    compressedDataStream.GetBuffer();

                // write out the chunk size
                size = BitConverter.GetBytes(chunkSize);
                await streamDestination.WriteAsync(size, 0, size.Length);

                // write out the compressed size
                size = BitConverter.GetBytes(compressedData.Length);
                await streamDestination.WriteAsync(size, 0, size.Length);

                // write out the compressed chunk
                await streamDestination.WriteAsync(compressedData, 0,
                    compressedData.Length);

                await streamDestination.FlushAsync();

                // subtract the chunk size from the file size
                fileLength -= chunkSize;

                // if chunk is less than remaining file use
                // remaining file
                if (fileLength < chunkSize)
                    chunkSize = fileLength;
            }
        }
    }
}

/// <summary>
/// This function will decompress the chunked compressed file
/// created by the CompressFile function.
/// </summary>
/// <param name="sourceFile">the compressed file</param>
/// <param name="destinationFile">the destination file</param>
/// <param name="compressionType">the type of compression to use</param>
public static async Task DecompressFileAsync(string sourceFile,
                                string destinationFile,
                                CompressionType compressionType)
{
    if (string.IsNullOrWhiteSpace(sourceFile))
        throw new ArgumentNullException(nameof(sourceFile));
    if (string.IsNullOrWhiteSpace(destinationFile))
        throw new ArgumentNullException(nameof(destinationFile));

    FileStream streamSource = null;
    FileStream streamDestination = null;
    Stream streamUncompressed = null;

    int bufferSize = 4096;
    using (streamSource = new FileStream(sourceFile,
            FileMode.OpenOrCreate, FileAccess.Read, FileShare.None,
            bufferSize, useAsync: true))
    {
        using (streamDestination = new FileStream(destinationFile,
            FileMode.OpenOrCreate, FileAccess.Write, FileShare.None,
            bufferSize, useAsync: true))
        {
            // read the fileLength size
            // read the chunk size
            byte[] size = new byte[sizeof(long)];
            await streamSource.ReadAsync(size, 0, size.Length);
            // convert the size back to a number
            long fileLength = BitConverter.ToInt64(size, 0);
            long chunkSize = 0;
            int storedSize = 0;
            long workingSet = Process.GetCurrentProcess().WorkingSet64;
            while (fileLength > 0)
            {
                // read the chunk size
                size = new byte[sizeof(long)];
                await streamSource.ReadAsync(size, 0, size.Length);
                // convert the size back to a number
                chunkSize = BitConverter.ToInt64(size, 0);
                if (chunkSize > fileLength ||
                    chunkSize > workingSet)
                    throw new InvalidDataException();

                // read the compressed size
                size = new byte[sizeof(int)];
                await streamSource.ReadAsync(size, 0, size.Length);
                // convert the size back to a number
                storedSize = BitConverter.ToInt32(size, 0);
                if (storedSize > fileLength ||
                    storedSize > workingSet)
                    throw new InvalidDataException();

                if (storedSize > chunkSize)
                    throw new InvalidDataException();

                byte[] uncompressedData = new byte[chunkSize];
                byte[] compressedData = new byte[storedSize];
                await streamSource.ReadAsync(compressedData, 0,
                    compressedData.Length);

                // uncompress the chunk
                MemoryStream uncompressedDataStream =
                    new MemoryStream(compressedData);

                if (compressionType == CompressionType.Deflate)
                    streamUncompressed =
                        new DeflateStream(uncompressedDataStream,
                            CompressionMode.Decompress);
                else
                    streamUncompressed =
                        new GZipStream(uncompressedDataStream,
                            CompressionMode.Decompress);

                using (streamUncompressed)
                {
                    // read the chunk in the compressed stream
                    await streamUncompressed.ReadAsync(uncompressedData, 0,
                        uncompressedData.Length);
                }

                // write out the uncompressed chunk
                await streamDestination.WriteAsync(uncompressedData, 0,
                    uncompressedData.Length);

                // subtract the chunk size from the file size
                fileLength -= chunkSize;

                // if chunk is less than remaining file use remaining file
                if (fileLength < chunkSize)
                    chunkSize = fileLength;
            }
        }
    }
}

public enum CompressionType
{
    Deflate,
    GZip
}