圧縮アルゴリズムを用意する(2)

 まずは手始めにzlibによる、データの圧縮方法について学びましょう。

 zlibはオブジェクト指向の概念がないC言語で開発されているため、クラスの代替策として専用の構造体に必要なデータを代入し、圧縮・解凍関数にそのデータを逐一渡すことで処理を行っています。構造体の中には関数内の処理のためだけにあり、プログラマが決して手を出してはいけない変数も含まれますので注意しましょう(C++だったらprivate属性にすることでアクセスをブロックできるのですが)。

 まずは、zlibに関するヘッダファイル「zlib.h」「zconf.h」の2つをインクルードします。続いて、メイン関数において、基本となる構造体「z_stream」を作成し、それをC++のコンストラクタに当たる圧縮初期化関数「deflateInit」にわたします。初期化関数にデータを渡す前に、圧縮のためのメモリ領域確保方法を指定しなくてはなりません。「z_stream.zalloc」にはメモリ確保を行う独自関数のポインタ、「z_stream.zfree」にはメモリ解放を行う独自関数のポインタ、「z_stream.opaque」には、メモリ確保/解放時に渡すオプションデータへのポインタを指定します。ただし、これらすべてに「Z_NULL(0)」を代入すると、メモリ処理はzlib標準の関数が適用されるため、特別な理由がない限りは「Z_NULL」による指定で構いません。deflateInitの第2引数にはデータの圧縮率を指定します。9で最高圧縮、0で無圧縮、Z_DEFAULT_COMPRESSION(-1)で標準的な圧縮となります。初期化に成功すれば「Z_OK(0)」が返されます。

#include "zlib.h"
#include "zconf.h"

void main()
{
	// z_stream構造体の初期化
	z_stream zs;
	zs.zalloc = Z_NULL;
	zs.zfree = Z_NULL;
	zs.opaque = Z_NULL;
	if(deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK){
		printf("初期化に失敗しました");
		return;
	}
}

 初期化が成功したら、zlibに圧縮するデータを渡していきます。ここで気をつけておきたいことは、データを渡してもすぐには圧縮が実行されないことです。zlibへデータを渡すと、まずはそのデータから、圧縮できるパターンをzlibは検知していきます(辞書の作成)。そして、データの受け渡しの終了を命令(フラッシュ)すると、それまでに蓄積されてきたパターンを元に、はじめて圧縮が実行されるのです。つまり、終了を宣言するまでは、メモリにどんどんデータが蓄積されていくことになります。かといって、あまり頻繁にフラッシュを行うと、圧縮パターンの情報不足より、圧縮効率が損なわれますので、何メガバイトごとに圧縮のフラッシュを行うかは、プログラムがzlib以外にどれだけメモリを利用するかによって検討する必要があるでしょう。

 圧縮を行う関数は「deflate」で、第2引数にバッファに対するフラッシュを指定します。「Z_NO_FLUSH」であればフラッシュをせずに、バッファへの蓄積を続行、「Z_SYNC_FLUSH」なら、フラッシュをできる範囲で行います。「Z_FULL_FLUSH」であれば、バッファを強制的にフラッシュします。「Z_FINISH」だと、フラッシュと同時にバッファへの終了処理が行われます。

 deflate関数が実行されると、z_stream.avail_outが更新されます。ここにはz_stream.next_outで指定した圧縮元データにおける、圧縮が完了していないバッファのサイズが入ります。逆に言えば、バッファサイズからこの値を引いたものが圧縮済みデータのサイズとなりますので、この値のサイズ分、ファイルなどに圧縮済みデータを出力することになります。

 Z_FINISHによるdeflate圧縮を終わった後は、デストラクタ代わりとして「deflateEnd」を呼び出して、z_stream構造体の中身を解放します(何らかのエラーで圧縮に失敗したときでも、処理の最後に呼び出さないといけません)。

 それでは、z_streamとdeflateによる一連の圧縮作業を行うサンプルプログラムをご紹介します。DeflateTestの第1引数には圧縮元となるファイルパスを、第2引数には圧縮されたデータを保存するファイルパスを指定してください。

#include "zlib.h"
#include "zconf.h"

#include 

const uInt buflimit = 1024 * 1024 * 1;	// 1MB
const uInt tempsize = 16384;

void DeflateTest(const char *infile, const char *outfile)
{
	// z_stream構造体の初期化
	z_stream zs;
	zs.zalloc = Z_NULL;
	zs.zfree = Z_NULL;
	zs.opaque = Z_NULL;
	if(deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK){
		printf("初期化に失敗しました");
		return;
	}

	FILE *ifp = fopen(infile, "rb");
	FILE *ofp = fopen(outfile, "wb");

	uInt buffersz = 0, rest;
	int flushtype;
	Byte intemp[tempsize], outtemp[tempsize];
	do{
		// 圧縮元データのサイズ
		zs.avail_in = fread(intemp, 1, tempsize, ifp);
		// 圧縮元データの格納されているバッファへのポインタ
		zs.next_in = intemp;

		buffersz += zs.avail_in;
		if(buffersz > buflimit){
			// zlibのバッファが一定量たまったら、いったん書き出す
			flushtype = Z_SYNC_FLUSH;
			buffersz = 0;
		}else if(feof(ifp) != 0){
			// ファイルの終端にきたら終了
			flushtype = Z_FINISH;
		}else{
			// データの取り込みを続行
			flushtype = Z_NO_FLUSH;
		}


		do{
			// 圧縮後データのサイズ
			zs.avail_out = tempsize;
			// 圧縮後データを格納するバッファのポインタ
			zs.next_out = outtemp;

			// データの転送
			deflate(&zs, flushtype);


			// avail_outに未圧縮データのサイズが格納されるので、
			// 差し引いた分のデータを圧縮データとして出力する
			rest = tempsize - zs.avail_out;
			
			// 圧縮済みデータの書き込み
			fwrite(outtemp, 1, rest, ofp);
		}while (zs.avail_out == 0);
	}while(flushtype != Z_FINISH);

	// zlibが使用したデータの解放
	deflateEnd(&zs);

	fclose(ifp);
	fclose(ofp);
}

 ビルドする際は「zlib.lib」をリンクすることをお忘れなく。