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 は以下のように実装できます.
class Encoder { public: Encoder() : out_(NULL), lzma_(), buf_(NULL), buf_size_(0), total_in_(0), total_out_(0) { lzma_stream initial_lzma = LZMA_STREAM_INIT; lzma_ = initial_lzma; } ~Encoder() { close(); } bool open(std::ostream *out, uint32_t preset = 6, lzma_check check = LZMA_CHECK_CRC64, std::size_t buf_size = 0) { close(); if (buf_size == 0) buf_size = DEFAULT_BUF_SIZE; try { buf_ = new uint8_t[buf_size]; buf_size_ = buf_size; } catch (...) { return false; } lzma_ret ret = lzma_easy_encoder(&lzma_, preset, check); if (ret != LZMA_OK) return false; lzma_.next_out = buf_; lzma_.avail_out = buf_size_; out_ = out; return true; } bool close() { bool is_ok = true; if (out_ != NULL) { is_ok = finish(); lzma_end(&lzma_); } if (buf_ != NULL) delete [] buf_; out_ = NULL; lzma_stream initial_lzma = LZMA_STREAM_INIT; lzma_ = initial_lzma; buf_ = NULL; buf_size_ = 0; return is_ok; } bool write(const void *data, std::size_t size) { if (out_ == NULL) return false; lzma_.next_in = static_cast<const uint8_t *>(data); lzma_.avail_in = size; while (lzma_.avail_in > 0) { lzma_ret ret = lzma_code(&lzma_, LZMA_RUN); if (ret != LZMA_OK) return false; if (lzma_.avail_out == 0) { if (!out_->write(reinterpret_cast<const char *>(buf_), buf_size_)) return false; lzma_.next_out = buf_; lzma_.avail_out = buf_size_; } } return true; } std::size_t total_in() const { return out_ != NULL ? lzma_.total_in : total_in_; } std::size_t total_out() const { return out_ != NULL ? lzma_.total_out : total_out_; } private: std::ostream *out_; lzma_stream lzma_; uint8_t *buf_; std::size_t buf_size_; std::size_t total_in_; std::size_t total_out_; enum { DEFAULT_BUF_SIZE = 4096 }; bool finish() { lzma_ret ret = LZMA_OK; do { ret = lzma_code(&lzma_, LZMA_FINISH); if (!out_->write(reinterpret_cast<const char *>(buf_), buf_size_ - lzma_.avail_out)) return false; lzma_.next_out = buf_; lzma_.avail_out = buf_size_; } while (ret == LZMA_OK); total_in_ = lzma_.total_in; total_out_ = lzma_.total_out; return ret == LZMA_STREAM_END; } // Disallows copy. Encoder(const Encoder &); Encoder &operator=(const Encoder &); };
class Decoder { public: Decoder() : in_(NULL), lzma_(), buf_(NULL), buf_size_(0), eof_(false), fail_(false), total_in_(0), total_out_(0) { lzma_stream initial_lzma = LZMA_STREAM_INIT; lzma_ = initial_lzma; } ~Decoder() {} bool open(std::istream *in, std::size_t buf_size = 0) { close(); if (buf_size == 0) buf_size = DEFAULT_BUF_SIZE; try { buf_ = new uint8_t[buf_size]; buf_size_ = buf_size; } catch (...) { return false; } lzma_ret ret = lzma_stream_decoder(&lzma_, lzma_easy_decoder_memusage(9), 0); if (ret != LZMA_OK) return false; in_ = in; return true; } void close() { if (in_ != NULL) lzma_end(&lzma_); if (buf_ != NULL) delete [] buf_; in_ = NULL; lzma_stream initial_lzma = LZMA_STREAM_INIT; lzma_ = initial_lzma; buf_ = NULL; buf_size_ = 0; eof_ = false; fail_ = false; } std::size_t read(void *buf, std::size_t size) { if (in_ == NULL || eof_ || fail_) return false; lzma_.next_out = static_cast<uint8_t *>(buf); lzma_.avail_out = size; while (lzma_.avail_out > 0) { if (lzma_.avail_in == 0) { in_->read(reinterpret_cast<char *>(buf_), buf_size_); lzma_.next_in = buf_; lzma_.avail_in = in_->gcount(); } if (!*in_) { lzma_ret ret = lzma_code(&lzma_, LZMA_FINISH); if (ret == LZMA_OK) continue; else { if (ret == LZMA_STREAM_END) { total_in_ = lzma_.total_in; total_out_ = lzma_.total_out; eof_ = true; } else fail_ = true; break; } } else { lzma_ret ret = lzma_code(&lzma_, LZMA_RUN); if (ret != LZMA_OK) { fail_ = true; break; } } } return size - lzma_.avail_out; } std::size_t total_in() const { return in_ != NULL ? lzma_.total_in : total_in_; } std::size_t total_out() const { return in_ != NULL ? lzma_.total_out : total_out_; } bool eof() const { return eof_; } bool fail() const { return fail_; } private: std::istream *in_; lzma_stream lzma_; uint8_t *buf_; std::size_t buf_size_; bool eof_; bool fail_; std::size_t total_in_; std::size_t total_out_; enum { DEFAULT_BUF_SIZE = 4096 }; // Disallows copy. Decoder(const Decoder &); Decoder &operator=(const Decoder &); };