[C#] GZip圧縮する(個人的な備忘録)

データの入出力には、色々な手段があります。一般的にはデータベースを使うことが多いのでしょうけど、研究開発的な計算結果とかだとCSVみたいなプリミティブな方法もよく使われます。ところが、データが大量になった場合にCSVのようなテキストファイル(アスキーファイル)を使っていると、読み取りに時間がかかる・ディスク容量を圧迫してしまうというような問題が生じてしまいます。

そこで、バイナリ形式で保存したりするのですが、最近ではCPUの性能も高くなっているので、手軽に圧縮して保存するという用途が出てきます。今日は個人的な備忘録として、C#でGZip圧縮&解凍するファイルを書いておきます。なお、ここでは、値を一括して書き込む場合と、その都度書き込む場合の二つの方法を書いています。二つの方法を試したのは、まとめて書き込んだ方が圧縮率が高くなるのではないかと見込んだからです。ところが、できたファイル容量を比べると全く同じでした。どうやら、GZipStreamがバッファを使ってその辺はうまくやってくれているようです。

下記のコードでは、int(Int32, 4Byte)を1024×1024個書き込んでいますので、無圧縮の場合には4MB(4MiB)となります。連続する整数値を書き込むこのコードの場合は1.38MBとなりましたので半分以下までデータを縮めることができました。ただし、ここでできたGZファイルを7-Zipで圧縮したら11.3KBまで縮みました。


2021/9/4追記
当初は書き込み時のGZipStreamのコンストラクタで「CompressionMode.Compress」としていました。この場合のCompressionLevelは「Optimal」になるようです。「CompressionLevel.Optimal」の場合、処理時間がかかる割に圧縮率が高くないようなので、ここは「CompressionLevel.Fastest」とした方が良いようです。下記のコードもそのように修正しました。

using System.IO;
using System.IO.Compression;

static void Main(string[] args) {
	//入出力ファイル名・データサイズ
	const string sGZ1 = @"C:\hogehoge\01_OneByOne.gz";
	const string sGZ2 = @"C:\hogehoge\02_AllAtOnece.gz";
	const string sTxt = @"C:\hogehoge\03.比較.csv";
	const int iLength = 1024 * 1024;

	//▼値を作成する
	int[] iValue = new int[iLength];
	for (int i = 0; i < iLength; i++) { iValue[i] = i; }

	//▼1:バラバラに保存する
	using (FileStream fs = new FileStream(sGZ1, FileMode.Create, FileAccess.Write))
	using (GZipStream gs = new GZipStream(fs, CompressionLevel.Fastest)) {
		for (int i = 0; i < iLength; i++) {
			gs.Write(BitConverter.GetBytes(iValue[i]), 0, sizeof(int)); 
		}
	}

	//▼2:一括で保存する
	//一括で保存するためのバイト列作成
	byte[] buf = new byte[iLength * sizeof(int)];
	for (int i = 0; i < iLength; i++) {
		for (int j = 0; j < sizeof(int); j++) { 
			buf[i * sizeof(int) + j] = BitConverter.GetBytes(iValue[i])[j];
		}
	}

	//バイト列を保存
	using (FileStream fs = new FileStream(sGZ2, FileMode.Create, FileAccess.Write))
	using (GZipStream gs = new GZipStream(fs, CompressionLevel.Fastest)) {
		gs.Write(buf, 0, buf.Length);
	}

	//▼検証:同じ値が保存されているか
	using (StreamWriter sw = new StreamWriter(sTxt, false, Encoding.ASCII))
	using (FileStream fs1 = new FileStream(sGZ1, FileMode.Open, FileAccess.Read))
	using (GZipStream gs1 = new GZipStream(fs1, CompressionMode.Decompress))
	using (FileStream fs2 = new FileStream(sGZ2, FileMode.Open, FileAccess.Read))
	using (GZipStream gs2 = new GZipStream(fs2, CompressionMode.Decompress)) {
		sw.WriteLine("OneByOne,AllAtOnece");

		byte[] bufOne = new byte[sizeof(int)];
		byte[] bufAll = new byte[sizeof(int)];

		for (int i = 0; i < iLength; i++) {
			if (gs1.Read(bufOne, 0, sizeof(int)) != sizeof(int)) { throw new Exception(""); }
			if (gs2.Read(bufAll, 0, sizeof(int)) != sizeof(int)) { throw new Exception(""); }
			int iOne = BitConverter.ToInt32(bufOne, 0);
			int iAll = BitConverter.ToInt32(bufAll, 0);
			sw.WriteLine("{0},{1}", iOne, iAll);
		}
	}
}