GZipStreamはgzip形式の圧縮・展開(伸張)を行うためのストリーム。 .NET FrameworkではHttpWebResponseクラスがgzipで圧縮されたコンテンツ(Content-Encoding: gzip)を展開する場合にGZipStreamを使用するが、ファイルの圧縮などにも利用することができる。

GZipStreamは圧縮と展開のどちらにも対応しているが、インスタンス生成時に圧縮用か展開用かどちらか一方を指定する必要がある。 このストリームに対して書き込み・読み込みを行うことにより、gzip形式での圧縮・展開することができる。

類似のクラスとして、LZ77形式の圧縮・展開を行うためのDeflateStreamというクラスもある。 gzip形式・LZ77形式のいずれも可逆圧縮なので、当然圧縮前と展開後のデータのサイズは同じになる。

§1 主なコンストラクタ

GZipStream(Stream, CompressionMode)
圧縮・展開を行う対象のストリーム、圧縮か展開のどちらの操作を行うか指定してインスタンスを生成する。 圧縮用にストリームを作成する場合はCompressionMode.Compress、展開用にストリームを作成する場合はCompressionMode.Decompressを指定する。
GZipStream(Stream, CompressionMode, Boolean)
圧縮/展開が終わってCloseしたあともストリームを開いたままにするかどうかを指定してインスタンスを生成する。 Trueを指定すると、GZipStreamを閉じてもベースとなるストリームは開かれたままになるので、圧縮・展開したストリームを使って追加の処理を行えるようストリームを再利用することができる。

§2 主なメソッド

void Write(byte[], int, int)
指定したバイト配列を圧縮し、ストリームに書き込む。
int Read(byte[], int, int)
ストリームから展開したデータを読み込み、指定したバイト配列に書き込む。

§3 主な呼び出し順序

圧縮する場合。

  1. new GZipStream()でCompressionMode.Compressを指定
  2. Write()
  3. Close()

展開する場合。

  1. new GZipStream()でCompressionMode.Decompressを指定
  2. Read()
  3. Close()

§4 使用例

§4.1 gzファイルの書き込み・読み込み

GZipStreamを使って圧縮済みのテキストファイル(.txt.gz)を書き込み・読み込みする。 未圧縮の一時ファイル(.txt)を生成せず、直接圧縮済みのファイル(.txt.gz)を読み書きする。 FileStreamクラスをGZipStreamクラスでラップすることにより、圧縮・展開を行った上でファイル内容の読み書きを行うことができる。

GZipStreamを使って圧縮済みのテキストファイル(.txt.gz)の書き込み・読み込みを行う
using System;
using System.IO;
using System.IO.Compression;

class Sample {
  static void Main()
  {
    // 出力先のファイルを開く
    using (var outputStream = File.Create("sample.txt.gz")) {
      // 出力先のストリームへの書き込み内容をGZipStreamで圧縮する
      using (var gzipStream = new GZipStream(outputStream, CompressionMode.Compress)) {
        // GZipStreamへ書き込むStreamWriterを作成する
        // (writerへ書き込む際、GZipStreamがその内容を圧縮した上でoutputStreamに書き込む)
        using (var writer = new StreamWriter(gzipStream)) {
          // writerを使って書き込む
          writer.WriteLine("かごめかごめ");
          writer.WriteLine("かごのなかのとりは");
          writer.WriteLine("いついつでやる");
        }
      }
    }

    // 入力元のファイルを開く
    using (var inputStream = File.OpenRead("sample.txt.gz")) {
      // 入力元のストリームから読み込んだ内容をGZipStreamで展開する
      using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress)) {
        // GZipStreamから読み込むStreamReaderを作成する
        // (readerから読み込む際、GZipStreamがその内容を展開した上でinputStreamから読み込む)
        using (var reader = new StreamReader(gzipStream)) {
          // readerを使って読み込む
          Console.WriteLine(reader.ReadToEnd());
        }
      }
    }
  }
}

gzip展開してファイル内容を表示するzcatコマンドを用いて上記のコードが生成するsample.txt.gzの内容を表示すると次のようになる。

出力ファイルsample.txt.gzの内容
$ zcat sample.txt.gz 
かごめかごめ
かごのなかのとりは
いついつでやる

この例のようにストリームを多段に重ねて使う方法についての詳細はストリームの基本とStreamクラス §.データフォーマットの変換やバッファリングなどの機能を追加するStream派生クラスを参照のこと。

§4.2 ファイルの圧縮・展開

GZipStreamを使ってテキストファイルの内容を圧縮して別のファイルに保存する。 また、圧縮したファイルを再び展開して別のファイルに保存する。 この例で使用しているStream.CopyToメソッドは.NET Framework 4以降でないと使用できないので注意。

GZipStreamを使ってファイルを圧縮・展開する
using System;
using System.IO;
using System.IO.Compression;

class Sample {
  static void Main()
  {
    Console.WriteLine("[Compression]");

    // 圧縮するファイルのFileStreamを作成する
    using (var inputStream = File.OpenRead("sample.txt")) {
      // 圧縮した結果を保存するファイルのFileStreamを作成する
      using (var outputStream = File.Create("sample.txt.gz")) {
        // データを圧縮してoutputStreamに書き込むためのGZipStreamを作成する
        // (GZipStreamを閉じた後でもoutputStream.Lengthを参照できるよう、leaveOpenはtrueにする)
        using (var gzipStream = new GZipStream(outputStream, CompressionMode.Compress, true)) {
#if STREAM_COPY
          // inputStreamからデータを読み出し、gzipStreamに書き込む
          inputStream.CopyTo(gzipStream);
#else
          // .NET Framework 4より前のバージョンではCopyToメソッドを使うことはできないので以下のようにする
          var buffer = new byte[1024];

          for (;;) {
            var len = inputStream.Read(buffer, 0, buffer.Length);

            if (len == 0)
              break;

            gzipStream.Write(buffer, 0, len);
          }
#endif
        }

        // 圧縮前後のファイルサイズを表示
        Console.WriteLine("original: {0:N0} bytes", inputStream.Length);
        Console.WriteLine("compressed: {0:N0} bytes ({1:P1})",
                          outputStream.Length, outputStream.Length / (double)inputStream.Length);
      }
    }

    Console.WriteLine("[Decompression]");

    // 展開するファイルのFileStreamを作成する
    using (var inputStream = File.OpenRead("sample.txt.gz")) {
      // inputStreamから読み込みデータを展開するためのGZipStreamを作成する
      // (GZipStreamを閉じた後でもinputStream.Lengthを参照できるよう、leaveOpenはtrueにする)
      using (var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress, true)) {
        // 展開した結果を保存するファイルのFileStreamを作成する
        using (var outputStream = File.Create("out.txt")) {
#if STREAM_COPY
          // gzipStreamから展開されたデータを読み出し、outputStreamに書き込む
          gzipStream.CopyTo(outputStream);
#else
          // .NET Framework 4より前のバージョンではCopyToメソッドを使うことはできないので以下のようにする
          var buffer = new byte[1024];

          for (;;) {
            var len = gzipStream.Read(buffer, 0, buffer.Length);

            if (len == 0)
              break;

            outputStream.Write(buffer, 0, len);
          }
#endif

          // 展開前後のファイルサイズを表示
          Console.WriteLine("original: {0:N0} bytes", inputStream.Length);
          Console.WriteLine("decompressed: {0:N0} bytes ({1:P1})",
                            outputStream.Length, outputStream.Length / (double)inputStream.Length);
        }
      }
    }
  }
}
.NET Framework 4での実行結果例
[Compression]
original: 3,208 bytes
compressed: 941 bytes (29.3%)
[Decompression]
original: 941 bytes
decompressed: 3,208 bytes (340.9%)
Mono 2.10.8での実行結果例
[Compression]
original: 3,208 bytes
compressed: 941 bytes (29.3%)
[Decompression]
original: 941 bytes
decompressed: 3,208 bytes (340.9%)

同じ内容のファイルについて、GZipStreamの代わりにDeflateStreamを使用して実行した例。

.NET Framework 4での実行結果例
[Compression]
original: 3,208 bytes
compressed: 923 bytes (28.8%)
[Decompression]
original: 923 bytes
decompressed: 3,208 bytes (347.6%)
Mono 2.10.8での実行結果例
[Compression]
original: 3,208 bytes
compressed: 923 bytes (28.8%)
[Decompression]
original: 923 bytes
decompressed: 3,208 bytes (347.6%)

ZIPアーカイブを作成したい場合は、System.IO.Compression.ZipArchiveクラスを用いることができる。

§5 圧縮レベルの指定

.NET Framework 4.5からは、GZipStream・DeflateStreamのコンストラクタにCompressionLevel列挙体の値を指定できるようになっている。 これにより圧縮時の圧縮レベルを指定することができる。 GZipStream・DeflateStreamに指定できる圧縮レベルは次のとおり。

CompressionLevel
CompressionLevel 圧縮レベル
CompressionLevel.Optimal 圧縮率優先
CompressionLevel.Fastest 圧縮速度優先
CompressionLevel.NoCompression 無圧縮

GZipStreamにCompressionLevelを指定して圧縮した場合の所要時間、圧縮率の違いは次の通り。

Windows 7 + .NET Framework 4.5での実行結果例
E:\>sample.exe sample.dat
input length: 681836
Optimal        : 00:00:00.0433742 14.3%
Fastest        : 00:00:00.0179198 16.7%
NoCompression  : 00:00:00.0104490 100.0%

E:\>sample.exe sample.txt
input length: 3208
Optimal        : 00:00:00.0010596 29.3%
Fastest        : 00:00:00.0002112 31.3%
NoCompression  : 00:00:00.0001500 100.7%

E:\>sample.exe sample.avi
input length: 76800512
Optimal        : 00:00:07.3913581 43.2%
Fastest        : 00:00:03.4181816 47.2%
NoCompression  : 00:00:01.1296466 100.0%
検証に使ったコード
using System;
using System.IO;
using System.IO.Compression;
using System.Diagnostics;

class Sample {
  static void Main(string[] args)
  {
    using (var inputStream = File.OpenRead(args[0])) {
      Console.WriteLine("input length: {0}", inputStream.Length);

      // CompressionLevelの値を列挙
      foreach (CompressionLevel compressionLevel in Enum.GetValues(typeof(CompressionLevel))) {
        Console.Write("{0,-15}: ", compressionLevel);

        // 初期サイズに元ファイルのサイズ分を確保してMemoryStreamを作成
        using (var outputStream = new MemoryStream((int)inputStream.Length)) {
          using (var gzipStream = new GZipStream(outputStream, compressionLevel, true)) {
            var sw = Stopwatch.StartNew();

            inputStream.Position = 0L;
            inputStream.CopyTo(gzipStream);

            // 圧縮にかかった時間を表示
            Console.Write("{0} ", sw.Elapsed);
          }

          // 圧縮率を表示
          Console.WriteLine("{0:P1}", outputStream.Length / (double)inputStream.Length);
        }
      }
    }
  }
}