Unix systems are commonly supplied
with some form of DBM or DB[47] data file, often called a
database. These are not
relational databases, but are
key/value pairs, rather like a
java.util.Hashtable
that is automatically
persisted to disk whenever you called its put( )
method. This format is also used on MS-Windows by
a few programs; for example, the Win32 version of Netscape keeps its
history in a history.db or
netscape.hst file, which is in this format. Not
convinced?
daroad.darwinsys.com$ pwd /c/program files/netscape/users/ian daroad.darwinsys.com$ file *.hst netscape.hst: Berkeley DB Hash file (Version 2, Little Endian, Bucket Size 4096, Bucket Shift 12, Directory Size 256, Segment Size 256, Segment Shift 8, Overflow Point 8, Last Freed 36, Max Bucket 184, High Mask 0xff, Low Mask 0x7f, Fill Factor 54, Number of Keys 733) daroad.darwinsys.com$
The
Unix file
command[48] decodes file types; it’s what Unix
people rely on instead of (or in addition to) filename extensions.
So the DBM format is a nice, general mapping from keys to values. But
how can we use it in Java? There is no publicly defined mapping for
Java, so I wrote my own. It uses a fair bit of native code, that is,
C code called from Java that in turn
calls the DBM library. I’ll discuss native code in Section 26.5. For now it suffices to know that we can
initialize a DBM file by calling the relevant constructor, passing
the name of our DB file. We can iterate over all the key/value pairs
by calling firstkey( )
once and then nextkey( )
repeatedly until it returns null
. Both
byte arrays and objects can be stored and retrieved; it is up to the
programmer to know which is which (hint: use one or the other within
a given DBM file). Objects are serialized using normal
Java object serialization (see Section 9.17). Here is
the API for the DBM
class:
public DBM(String fileName) throws IOException; public Object nextkey(Object) throws IOException; public byte[] nextkey(byte[]) throws IOException; public Object firstkeyObject( ) throws IOException; public byte[] firstkey( ) throws IOException; public void store(Object,Object) throws IOException; public void store(byte[],byte[]) throws IOException; public Object fetch(Object) throws IOException; public byte[] fetch(byte[]) throws IOException; public void close( );
A simple program to print out the sites we have visited as listed in our Netscape history is shown in Example 20-3.
Example 20-3. ReadHistNS.java
import java.io.IOException; /** Demonstration of reading the MS-Windows Netscape History * under UNIX using DBM.java. */ public class ReadHistNS { public static void main(String[] unused) throws IOException { DBM d = new DBM("netscape.hst"); byte[] ba; for (ba = d.firstkey( ); ba != null; ba = d.nextkey(ba)) { System.out.println("Key="" + new String(ba) + '"'), byte[] val = d.fetch(ba); for (int i=0; i<16&&i<val.length; i++) { System.out.print((short)val[i]); System.out.print(' '), } } } }
The DBM format is an emulation of an older format, built on top of
the DB library. Because of this, the filename must end in
.pag
, so I copied the history file to the name
shown in the DBM constructor call.
A longer program, which includes both storing and retrieving in a DBM
file, is the DBM version of the UserDB
class,
UserDBDBM
. This is shown in Example 20-4.
Example 20-4. UserDBDBM.java
package jabadot; import java.io.*; import java.util.*; import java.sql.SQLException; /** A trivial "database" for User objects, stored in a flat file. * <P> * Since this is expected to be used heavily, and to avoid the overhead * of re-reading the file, the "Singleton" Design Pattern is used * to ensure that there is only ever one instance of this class. */ public class UserDBDBM extends UserDB { protected final static String DEF_NAME = "/home/ian/src/jabadot/userdb"; // It appends .pag protected DBM db; /** Default Constructor */ protected UserDBDBM( ) throws IOException,SQLException { this(DEF_NAME); } /** Constructor */ protected UserDBDBM(String fn) throws IOException,SQLException { super( ); db = new DBM(fn); String k; Object o; // Iterate through contents of DBM, adding into list. for (o=db.firstkeyObject( ); o!=null; o=db.nextkey(o)) { // firstkey/nextkey give Key as Object, cast to String. k = (String)o; o = db.fetch(k); // Get corresponding Value (a User) users.add((User)o); // Add to list. } } /** Add one user to the list, both in-memory and on disk. */ public synchronized void addUser(User nu) throws IOException, SQLException { // Add it to the in-memory list super.addUser(nu); // Add it to the on-disk version: store in DB with // key = nickname, value = object. db.store(nu.getName( ), nu); } }
SleepyCat software (http://www.sleepycat.com) provides an improved version of Berkeley DBM and includes a Java driver for it. The Free Software Foundation provides GDBM, another DBM-like mechanism.
[47] DBM is the original format; DB is a newer, more general format. DBM is actually now a front-end to DB, but because it’s a bit simpler, I’ve used it for this example. GDBM is the FSF’s implementation.
[48] The version of file(1)in Linux and BSD systems was originally written by your humble scribe.
3.147.85.181