bzlibでbz2ファイルを読み書きしてみる

bz2はメモリを喰うがzlibより圧縮効率がよいらしいので,そのライブラリbzlibを試してみた.
bzlib.h
圧縮
エラーメッセージの取得
解凍
コンパイルと実行

bzlib.h

bzlibのヘッダファイルはbzlib.hで,どんな関数があるかを一覧できる. bzlibは以下でインストールする.
apt-get install libbz2-dev
で,bzlibを使用する際の大まかな流れは,zlibと同様のパターンで,プログラム上は,ほぼ関数名が異なるだけと言ってよいような状況のようだ. そこで今回のサンプルは,前頁のzlibのサンプルを単にbzlib版に変更したものにした.

圧縮

データをbz2形式圧縮してファイル出力するサンプルを以下に示す.

include

include

include

define BZ2_MODE “wb”

int
main(int argc, char *argv[])
{
if (argc != 4)
{
fprintf(stderr, “Usage : %s filename string count\n”, argv[0]);
exit(1);
}

// 出力回数
int count = atoi(argv[3]);

// 出力文字合計
unsigned long sum = 0L;

// bz2出力ファイルをオープンする.
BZFILE *outFile = BZ2_bzopen(argv[1], BZ2_MODE);
if (outFile == NULL)
{
fprintf(stderr, “failed to BZ2_bzopen\n”);
exit(1);
}

int i;
for (i = 0; i < count; i++)
{
// データをbz2形式で圧縮して出力する.
int writeLen = BZ2_bzwrite(outFile, argv[2], strlen(argv[2]));
if (writeLen != strlen(argv[2]))
{
fprintf(stderr, “failed to BZ2_bzwrite\n”);
exit(1);
}
sum += writeLen;
}
printf(“wrote %lu\n”, sum);

// bz2ファイルのクローズ
BZ2_bzclose(outFile);

return 0;
}

簡易APIを使用すると,通常のファイル出力やzlib使用時と同様,オープン,書き出し,クローズの三段階の手順となる.
ファイルのオープン
ファイルのオープンはfopenに似ている.
BZFILE *
BZ2_bzopen(
const char *path,
const char *mode
);
pathが出力ファイルパス,modeは(たぶん)fopenのモードで”rb”か”wb”である. BZ2_bzopenで返されるBZFILEオブジェクトが,今後の各bzlib関数に渡すハンドラとなる. エラー時は返り値がNULLとなる.
圧縮と書き出し
データの圧縮と書き出しは一つの関数呼び出しで行える.
BZ_EXTERN int
BZ2_bzwrite(
BZFILE* b,
void* buf,
int len
);
第一引数はBZ2_bzopenの返りを使用する. あとは書き出しデータ(圧縮前)が格納されたバッファとその長さである. 返り値は書き出されたデータの圧縮前の長さが返される.
クローズ
クローズはfcloseと同様,簡単. 第一引数はgzopenの返りを使用する.
BZ_EXTERN void
BZ2_bzclose(
BZFILE* b
);
gzcloseと異なり,返り値はない.

エラーメッセージの取得

zlibと同様,bzlibでも関数呼び出しでエラー番号が返された場合,そのエラー番号からエラーメッセージを取得できる.
BZ_EXTERN const char *
BZ2_bzerror(
BZFILE *b,
int *errnum
);
返り値がerrnumに対応するエラーメッセージである. 第一引数はBZ2_bzopenの返りであり,クローズされたものは使用できない. だから,BZ2_bzopenやBZ2_bzclose失敗時には使用できない. 第二引数についてはよくわからない.

解凍

bz2形式ファイルを解凍して読み出すサンプルを以下に示す.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bzlib.h>

#define BZ2_MODE "rb"
#define BUF_SIZE (512)

int
main(int argc, char *argv[])
{
  if (argc != 2)
    {
      fprintf(stderr, "Usage : %s filename\n", argv[0]);
      exit(1);
    }

  int fd = open(argv[1], O_RDONLY);
  if (fd < 0)
    {
      perror("failed to open");
      exit(1);
    }

  BZFILE *inFile = BZ2_bzdopen(fd, BZ2_MODE);
  if (inFile == NULL)
    {
      fprintf(stderr, "failed to BZ2_bzdopen.\n");
      exit(1);
    }
  int ret;
  unsigned long sum = 0;
  char buf[BUF_SIZE];
  while((ret = BZ2_bzread(inFile, buf, sizeof(buf))) != 0
        && ret != -1)
    {
      sum += ret;
    }
  if(ret == -1)
    {
      // BZ2_bzerrorでエラーメッセージを取得する
      const char *msg = BZ2_bzerror(inFile, &ret);
      fprintf(stderr, "BZ2_bzread failed. %s\n", msg);
      exit(1);
    }
  else
    {
      printf("read %lu\n", sum);
    }

  BZ2_bzclose(inFile);

  return 0;
}
簡易APIを使用すると,やはり通常のファイル出力と同様,オープン,読み込み,クローズの三段階の手順となる. サンプルのクローズは圧縮時と同様である. オープンは圧縮時とは別の方法としてファイルディスクリプタを指定する方法を使用している. もちろん圧縮時と同様の方法も使用できるし,ファイルディスクリプタを使用する方法を圧縮時に使用することもできる.
ファイルディスクリプタ指定のオープン
gzdopenと同様,BZ2_bzdopenはファイルディスクリプタ指定でオープンできる.
BZ_EXTERN BZFILE *
BZ2_bzdopen(
  int        fd,
  const char *mode
);
ファイルディスクリプタを引数にもつので,圧縮データの読み書き対象がファイルだけでなく,ネットワークストリームやパイプであってもbzlibが使用できる. 当然ファイルディスクリプタは標準関数を使用してオープンしておく必要がある. 後でBZ2_bzcloseした場合,ファイルディスクリプタもクローズされる. 返り値はBZ2_bzopenと同様.
解凍と読み込み
解凍と読み込みは,一つの関数呼び出しでできる.
BZ_EXTERN int
BZ2_bzread(
  BZFILE* b, 
  void* buf, 
  int len 
);
第一引数はBZ2_bzopenやBZ2_bzopenの返り値,bufは読み込み用バッファ,lenは読み込む解凍後のデータサイズ, 返り値は読み込んだデータの解凍後のバイト数である.

コンパイルと実行

コンパイルする際は,リンカオプションを設定する.
LDFLAGS=-lbz2
では,圧縮,解凍と続けて実行してみる.
# 圧縮
$ ./mybz2write outfile.bz2 FIREPROJECT 1000
wrote 11000

# 出力サイズは11000文字に対して69バイトなので,圧縮されているようだ
$ ls -l outfile.bz2 
-rw-r--r-- 1 matsu matsu 69 2007-04-30 12:58 outfile.bz2

# 解凍. 正しく11000文字に解凍されている.
$ ./mybz2read outfile.bz2 
read 11000

# ツールで出力を解凍,表示
$ bzcat outfile.bz2 
FIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECTFIREPR...省略...
$ bzcat outfile.bz2 | wc -c
11000

This article was written by Fujiko