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

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 &);
};

おまけ

圧縮プリセット lzma_easy_encoder_memusage() lzma_easy_decoder_memusage()
0 1511943 131112
1 1511943 131112
2 5083655 589864
3 13210123 1114152
4 25268747 2162728
5 49385995 4259880
6 97620491 8454184
7 194089483 16842792
8 387027467 33620008
9 705794571 67174440