XZ Utils(liblzma)による圧縮・伸長(C++)
xz の概要
xz(XZ Utils)は lzma(LZMA Utils)の後継となる圧縮形式です.ちょっとした性能テスト(コマンド xz の圧縮率と圧縮速度 - やた@はてな日記,コマンド xz による圧縮・伸長のメモリ消費 - やた@はてな日記)でも分かるように,圧縮にかかるコストが大きいものの,データをより小さく圧縮できます.
xz ファイルを操作するコマンド
XZ Utils をインストールすれば,コマンド xz が使えるようになります.使い方は gzip や bzip2 とほどんど同じです."-c" で出力先を標準出力にしたり,"-d" で伸長モードになったり,"-k" で入力ファイルを残したりという具合です.
# Ubuntu 10.4 では,"sudo aptitude install xz-utils" でインストールできました.対応する開発用のパッケージは liblzma-dev です.
また,バージョン 1.22 以降の tar では,gzip(z)や bzip2(j)と同じように xz(J)も利用できます.
liblzma の使い方
liblzma を使うために必要なヘッダは <lzma.h> です.ライブラリのリンクは,-llzma で指定できます.関数や型,定数の説明は,ヘッダに書いてあります.基本的な機能(圧縮プリセットを用いた圧縮・伸長)については,以下のヘッダを見ると良いと思います.
ライブラリのインタフェースは,zlib や bzlib と似ています.ストリーム(lzma_stream)を初期化した後,入出力(next_in, avail_in, next_out, avail_out)を設定してから,圧縮・伸長(lzma_code())をおこないます.最後の入力を与えるとき,lzma_code() に LZMA_FINISH を渡すようにします.そして,圧縮・伸長が終われば,lzma_end() で終了処理をおこないます.
以下,圧縮と伸長の手順について,もう少し詳しく説明します.
liblzma による圧縮
ストリームの初期設定
まず,ストリームの構造体を初期化します.
// LZMA_STREAM_INIT はマクロで,lzma_stream の初期化にのみ利用できます. lzma_stream stream = LZMA_STREAM_INIT; // 既に確保済みの lzma_stream を初期化しなおす場合は, // LZMA_STREAM_INIT で初期化した lzma_stream を代入します. // lzma_stream temp_stream = LZMA_STREAM_INIT; // stream = temp_stream;
次に,圧縮用の初期設定をおこないます.
// 第 1 引数には,初期化した lzma_stream を渡します. // 第 2 引数には,圧縮プリセットを指定します. // 0 以上 9 以下の整数と,LZMA_PRESET_EXTREME を併せて指定できます. // 数値を大きくするほど圧縮率が高くなりますが,圧縮に時間がかかり, // メモリ消費が大きくなります. // LZMA_PRESET_EXTREME の効果については,xz --help で表示される // オプション -e の説明が分かりやすいと思います. // 指定する場合,6 | LZMA_PRESET_EXTREME のようにします. // 第 3 引数には,整合性チェックの方法を指定します. // よく分からないときは LZMA_CHECK_CRC32 を使えと lzma/container.h に // 書いてありますが,コマンド xz は LZMA_CHECK_CRC64 を使っているようです. lzma_ret ret = lzma_easy_encoder(&stream, 6, LZMA_CHECK_CRC64); if (ret != LZMA_OK) エラー処理;
圧縮プリセットを用いた場合のメモリ使用量は,lzma_easy_encoder_memusage() で確認できます.
入出力の設定
ストリームに入力データと出力先のバッファを指定します.指定の方法はメンバに代入するだけですが,uint8_t を使用しているので,型キャストが必要になると思います.
void set_input(lzma_stream *stream, const void *data, std::size_t size) { // data が const char * の場合,reinterpret_cast を用いるか, // const void * を経由することになります. stream->next_in = static_cast<const uint8_t *>(data); stream->avail_in = size; } void set_output(lzma_stream *stream, void *buf, std::size_t size) { stream->next_out = static_cast<uint8_t *>(buf); stream->avail_out = size; }
圧縮
入出力の設定が終われば,lzma_code() を呼び出します.例えば,以下のようなループになります.
// 入力が尽きるまで圧縮を続けます. while (stream.avail_in > 0) { lzma_ret ret = lzma_code(&stream, LZMA_RUN); if (ret != LZMA_OK) エラー処理; if (stream.avail_in == 0) 入力の続きを設定; if (stream.avail_out == 0) 出力先のバッファを再設定; }
最後の入力を指定した後は,LZMA_FINISH とともに呼び出します.圧縮が終了すれば,lzma_code() は LZMA_STREAM_END を返します.
lzma_ret ret; do { ret = lzma_code(&stream, LZMA_FINISH); if (ret == LZMA_OK) 出力先のバッファを再設定; else if (ret != LZMA_STREAM_END) エラー処理; } while (ret == LZMA_OK);
liblzma による伸長
ストリームの初期設定
圧縮時と同じようにストリームの構造体を初期化した後,lzma_stream_encoder() を用いて伸長用の初期設定をおこないます.
// 第 1 引数には,初期化した lzma_stream を渡します. // 第 2 引数には,メモリ使用量の上限を指定します. // メモリ使用量が上限に達すると,lzma_code() はエラーを返します. // lzma_memlimit_set() を使っても上手く復帰できないようなので, // 最初から大きな値を指定しておいた方がよさそうです. // 基本的な使い方であれば,圧縮プリセットに 9 を指定したときの // メモリ使用量(64MiB 強)で足りると思います. // 第 3 引数にはオプションを指定できますが,とりあえず 0 にしておきました. lzma_ret ret = lzma_stream_decoder(&stream, lzma_easy_decoder_memusage(9), 0); if (ret != LZMA_OK) エラー処理;
残りの手順
入出力の設定・伸長・ストリームの終了処理については,圧縮の場合とほぼ同じになります.異なるのは,圧縮されているデータが入力となり,伸長した後のデータが出力となることくらいです.
サンプルコード
データを圧縮してストリームに出力するクラス Encoder,圧縮データをストリームから入力して伸長するクラス Decoder は以下のように実装できます.