Storing Custom Objects

Up to this point, you have only stored byte arrays or strings in a record store. As explained in the diary example, Strings have a method with which you can convert them to byte arrays, and a constructor that takes a byte array as input. But if you want to store your own objects, you need to provide a way to convert them to byte arrays and back yourselves.

The simplest solution would be to add the same constructor and getBytes() method to your custom objects that String already provides. You can implement the mapping two different ways. One possibility is to convert the byte array to different fields in your object. The other possibility is to keep the byte array as storage, and to implement field access methods as wrappers to portions of the byte array. The second possibility makes sense especially when your structure consists of elements having a fixed size. Here, we will focus on the first method, which is simpler to handle, especially for dynamic structures.

Data Streams

Assume you are going to implement a travel list management tool, where the entries consist of the journey destination, the date of the journey and the distance actually traveled.

Such an object could be implemented as follows:

class Journey {
    int distance;
    long date;
    String destination;
}

The simplest mechanism to serialize this kind of object is provided by a combination of a DataOutputStream and a ByteArrayOutputStream. The DataOutputStream allows you to write simple data types such as integers or byte arrays to an OutputStream, and the ByteArrayOutputStream is a special OutputStream that creates a byte array. To get a byte array representation of the whole object, you plug them together in the getBytes() method of your class Journey. There, you write the fields to the stream and finally obtain the byte array from the ByteArrayOutputStream:

byte [] getBytes () throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream ();
    DataOutputStream dos = new DataOutputStream (baos);
    dos.writeInt (distance);
    dos.writeLong (date);
    dos.writeUTF (destination);
    dos.close ();
    baos.close ();
    return baos.toByteArray ();
}

In the constructor, you can use a ByteArrayInputStream and a DataInputStream to perform the inverse operation:

Journey (byte [] data) throws IOException {
     ByteArrayInputStream bais = new ByteArrayInputStream (data);
     DataInputStream dis = new DataInputStream (bais);
     distance = dis.readInt ();
     date = dis.readLong ();
     destination = dis.readUTF ();
     dis.close ();
     bais.close ();
 }

Direct Encoding

An alternative to using a set of streams—which may be faster but which also generates more implementation effort—is to decode the information in the byte array manually. For example, if the integer storing the distance is contained in the first four bytes of the byte array, it can be decoded using the following line:

distance = ((data [0] & 0xff) << 24) | ((data [1] & 0xff) << 16)
           | ((data [2] & 0xff) << 8) | (data [3] & 0xff);

The corresponding inverse operation is

data [0] = (byte) (0xff & (distance >> 24));
data [1] = (byte) (0xff & (distance >> 16));
data [2] = (byte) (0xff & (distance >> 8));
data [3] = (byte) (0xff & distance);

Because of the additional implementation effort, this method makes sense only in time-critical applications where the overhead of creating two additional streams is too expensive.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset