# QDBM付属Java用API仕様書

Copyright (C) 2000-2006 Mikio Hirabayashi
Last Update: Thu, 26 Oct 2006 15:00:20 +0900
[API] [English]

## 目次

1. 概要
2. インストール
3. 環境設定
4. サンプルコード
5. バグ

## 概要

QDBMにはJava言語用のAPIがある。QDBMの基本APIと拡張APIと上級APIの関数群をJavaのクラス機構を用いてカプセル化し、かつスレッドセーフにしたものである。C言語のAPIをJava Native Interfaceを介して呼び出すように実装されている。

基本APIはファイルを用いてハッシュデータベースを実現する。クラス `Depot' のコンストラクタによってデータベースファイルが開かれる。データベースを閉じるにはメソッド `close' を呼ぶ。ファイナライザでもデータベースを閉じようとするが、それに頼ってはならない。メソッド `put' はレコードを追加するために用いる。メソッド `out' はレコードを削除するために用いる。メソッド `get' はレコードを検索するために用いる。その他にも、C言語の基本APIとほぼ同じ操作を利用することができる。各メソッドはエラー時にクラス `DepotException' のインスタンスを投げる。

拡張APIはディレクトリと複数のファイルを用いてハッシュデータベースを実現する。クラス `Curia' のコンストラクタによってデータベースディレクトリが開かれる。データベースを閉じるにはメソッド `close' を呼ぶ。ファイナライザでもデータベースを閉じようとするが、それに頼ってはならない。メソッド `put' はレコードを追加するために用いる。メソッド `out' はレコードを削除するために用いる。メソッド `get' はレコードを検索するために用いる。その他にも、C言語の拡張APIとほぼ同じ操作を利用することができる。各メソッドはエラー時にクラス `CuriaException' のインスタンスを投げる。

上級APIはファイルを用いてB+木データベースを実現する。クラス `Villa' のコンストラクタによってデータベースファイルが開かれる。データベースを閉じるにはメソッド `close' を呼ぶ。ファイナライザでもデータベースを閉じようとするが、それに頼ってはならない。メソッド `put' はレコードを追加するために用いる。メソッド `out' はレコードを削除するために用いる。メソッド `get' はレコードを検索するために用いる。その他にも、C言語の上級APIとほぼ同じ操作を利用することができる。各メソッドはエラー時にクラス `VillaException' のインスタンスを投げる。

`Depot' と `Curia' と `Villa' はインタフェース `ADBM' を実装する。このインタフェースはUNIX標準のDBMと同様の機能を持つデータベースマネージャを抽象化したものである。各メソッドはエラー時にクラス `DBMException' のインスタンスを投げる。四つのAPIから適切なものを選択する際には、実行効率を重視するなら `Depot' を、スケーラビリティを重視するなら `Curia' を、順序に基づく参照が必要なら `Villa' を、エレガンスと保守性を重視するなら `ADBM' を選ぶべきであろう。データベースファイルは各APIの間で互換性がない。

各クラスはパッケージ `qdbm' に含まれ、アプリケーションのソースファイルでそれをインポートすることができる。

CのAPIは、スレッド間でデータベースハンドルを共有しない限りはスレッドセーフである。JavaのAPIでは、複数のスレッドが同じハンドルにアクセスしてもスレッドセーフである。

`put' で既存のレコードの上書きがキャンセルされた際や `get' で存在しないレコードが検索された際には例外によって操作の失敗が通知されるが、それが鬱陶しい場合は `silent' フラグを真にするとよい。その場合は失敗が戻り値によって通知される。

APIの詳細に関しては、サブディレクトリ `japidoc' の文書を参照すること。

## インストール

### 準備

JDKの1.2以降のバージョンがインストールされ、環境変数 `JAVA_HOME' が適切に設定され、QDBMが `/usr/local' 以下にインストールされていることが必要である。

インストール作業は、サブディレクトリ `java' をカレントディレクトリにして行う。

    cd java

### 普通の手順

ビルド環境を設定する。JavaのコンパイルにGCCを用いる場合、`--with-gcj' オプションを付ける。

    ./configure

プログラムをビルドする。

    make

プログラムの自己診断テストを行う。

    make check

プログラムをインストールする。作業は `root' ユーザで行う。

    make install

一連の作業が終ると、ネイティブライブラリ `libjqdbm.so' 等とJARファイル `qdbm.jar' が `/usr/local/lib' にインストールされる。

アンインストールするには、`./configure' をした後の状態で以下のコマンドを実行する。作業は `root' ユーザで行う。

    make uninstall

### Windowsの場合

Windows（Cygwin）にインストールする場合、以下の手順に従う。

ビルド環境を設定する。

    ./configure

プログラムをビルドする。

    make win

プログラムの自己診断テストを行う。

    make check-win

プログラムをインストールする。なお、アンインストールする場合は `make uninstall-win' とする。

    make install-win

Windowsでは、インポートライブラリ `libjqdbm.dll.a' が生成され、さらにダイナミックリンクライブラリ `jqdbm.dll' が生成される。`jqdbm.dll' は `/usr/local/bin' にインストールされる。

Cygwin環境でMinGWを用いてビルドするには、`make win' の代わりに `make mingw' を用いる。CygwinのUNIXエミュレーション層を用いる場合、生成されるプログラムは `cygwin1.dll' に依存したものになる。MinGWによってWin32のネイティブDLLとリンクさせればこの問題を回避できる。

### Mac OS Xの場合

Mac OS X（Darwin）にインストールする場合、以下の手順に従う。

ビルド環境を設定する。

    ./configure

プログラムをビルドする。

    make mac

プログラムの自己診断テストを行う。

    make check-mac

プログラムをインストールする。なお、アンインストールする場合は `make uninstall-mac' とする。

    make install-mac

Mac OS Xでは、`libjqdbm.so' 等の代わりに `libjqdbm.dylib' や `libjqdbm.jnilib' 等が生成される。

### HP-UXの場合

HP-UXにインストールする場合、以下の手順に従う。

ビルド環境を設定する。

    ./configure

プログラムをビルドする。

    make hpux

プログラムの自己診断テストを行う。

    make check-hpux

プログラムをインストールする。なお、アンインストールする場合は `make uninstall-hpux' とする。

    make install-hpux

HP-UXでは、`libjqdbm.so' 等の代わりに `libjqdbm.sl' が生成される。

## 環境設定

QDBMを利用したJavaプログラムをビルドしたり、それを実行したりするには、環境変数を設定しておく必要がある。

クラスパスを設定する。環境変数 `CLASSPATH' の値がJARファイルのフルパスを含むようにする。

    CLASSPATH=$CLASSPATH:/usr/local/lib/qdbm.jar
    export CLASSPATH

ライブラリパスを設定する。環境変数 `LD_LIBRARY_PATH' の値がライブラリのあるディレクトリを含むようにする。なお、Windowsではこの設定は不要であり、Mac OS Xでは環境変数 `DYLD_LIBRARY_PATH' を用い、HP-UXでは環境変数 `SHLIB_PATH' を用いる。

    LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
    export LD_LIBRARY_PATH

## サンプルコード

名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。

    import qdbm.*;

    public class Sample {

      static final String NAME = "mikio";
      static final String NUMBER = "000-1234-5678";
      static final String DBNAME = "book";

      public static void main(String[] args){
        Depot depot = null;
        try {

          // データベースを開く
          depot = new Depot(DBNAME, Depot.OWRITER | Depot.OCREAT, -1);

          // レコードを格納する
          depot.put(NAME.getBytes(), NUMBER.getBytes());

          // レコードを取得する
          byte[] res = depot.get(NAME.getBytes());
          System.out.println("Name: " + NAME);
          System.out.println("Number: " + new String(res));

        } catch(DepotException e){
          e.printStackTrace();
        } finally {

          // データベースを閉じる
          if(depot != null){
            try {
              depot.close();
            } catch(DepotException e){
              e.printStackTrace();
            }
          }

        }
      }

    }

上記の例を `ADBM' クラスを用いて書き直した例を以下に示す。

    import qdbm.*;

    public class Sample {

      static final String NAME = "mikio";
      static final String NUMBER = "000-1234-5678";
      static final String DBNAME = "book";

      public static void main(String[] args){
        ADBM dbm = null;
        try {

          // データベースを開く
          dbm = new Depot(DBNAME, Depot.OWRITER | Depot.OCREAT, -1);

          // レコードを格納する
          dbm.store(NAME.getBytes(), NUMBER.getBytes(), true);

          // レコードを取得する
          byte[] res = dbm.fetch(NAME.getBytes());
          System.out.println("Name: " + NAME);
          System.out.println("Number: " + new String(res));

        } catch(DBMException e){
          e.printStackTrace();
        } finally {

          // データベースを閉じる
          if(dbm != null){
            try {
              dbm.close();
            } catch(DBMException e){
              e.printStackTrace();
            }
          }

        }
      }

    }

`Villa' クラスを用いて文字列の前方一致検索を行う例を以下に示す。

    import qdbm.*;

    public class Sample {

      static final String DBNAME = "words";
      static final String PREFIX = "apple";

      public static void main(String[] args){
        Villa villa = null;
        try {

          // データベースを開く
          villa = new Villa(DBNAME, Villa.OWRITER | Villa.OCREAT, Villa.CMPOBJ);

          // レコードを格納する
          villa.putobj("applet", "little application", Villa.DDUP);
          villa.putobj("aurora", "polar wonderwork", Villa.DDUP);
          villa.putobj("apple", "delicious fruit", Villa.DDUP);
          villa.putobj("amigo", "good friend", Villa.DDUP);
          villa.putobj("apple", "big city", Villa.DDUP);

          try {

            // カーソルを候補の先頭に置く
            villa.curjumpobj(PREFIX, Villa.JFORWARD);

            // カーソルを走査する
            for(;;){
              String key = (String)villa.curkeyobj();
              if(!key.startsWith(PREFIX)) break;
              String val = (String)villa.curvalobj();
              System.out.println(key + ": " + val);
              villa.curnext();
            }

          } catch(VillaException e){
            if(e.ecode != Villa.ENOITEM) throw e;
          }
        } catch(VillaException e){
          e.printStackTrace();
        } finally {

          // データベースを閉じる
          if(villa != null){
            try {
              villa.close();
            } catch(VillaException e){
              e.printStackTrace();
            }
          }

        }
      }

    }

Java用APIを利用したプログラムをビルドするには、クラスパスを適切に設定した上で、`javac' を実行する。例えば、`Sample.java' から `Sample.class' を作るには、以下のようにビルドを行う。

    javac Sample.java

## バグ

ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。ひとつのデータベースを複数のスレッドで利用する場合には、メインスレッドで生成したハンドルを他のスレッドに渡せばよい。

オブジェクトを直列化してデータベースに格納する手法は便利であるが、オブジェクトの直列化は時間的および空間的に効率がよくない。明示的にバイト配列に変換できるならば、なるべくバイト配列を格納した方がよい。また、ハッシュデータベースのキーの比較はオブジェクトに対しても直列化した状態で行われる。すなわち、二つのオブジェクトが直列化した状態で完全に一致しない場合は、たとえ `equals' の値が真であっても、一致したキーとはみなされない。B+木データベースでは比較関数を適切に指定できるのでこの問題はない。