XZ Utils(liblzma)による圧縮・伸長(C++)

xz の概要

xz(XZ Utils)は lzmaLZMA 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 で指定できます.関数や型,定数の説明は,ヘッダに書いてあります.基本的な機能(圧縮プリセットを用いた圧縮・伸長)については,以下のヘッダを見ると良いと思います.

  • lzma/base.h
    • lzma_ret: 各種関数の返り値(エラーコード)
    • lzma_action: lzma_code() の動作指定(継続 or 終了)
    • lzma_stream: ストリームの構造体
    • lzma_code(): 圧縮・伸長
    • lzma_end(): ストリームの終了処理(メモリの解放)
  • lzma/container.h
    • lzma_easy_encoder_memusage(): 圧縮時のメモリ使用量を取得
    • lzma_easy_decoder_memusage(): 伸長時のメモリ使用量を取得
    • lzma_easy_encoder(): ストリームを圧縮用に初期設定
    • lzma_stream_decoder(): ストリームを伸長用に初期設定
  • lzma/check.h
    • lzma_check: 整合性チェックの方法

ライブラリのインタフェースは,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);
ストリームの終了処理

圧縮が終了すれば,割り当てたメモリを解放するために,lzma_end() を呼び出します.

lzma_end(&stream);

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 は以下のように実装できます.

続きを読む