prev back next

Binary object storage

Contents

Introduction

This document will teach you how to store and retrieve objects on/from an external medium.

Smalltalk offers various ways to store and retrieve objects to/from the external world. Beside the wellknown storeOn: method, binary storage is supported by any object.
Binary storage is both more dense (i.e. requires less space) and faster than textual storage in the normal case. In addition, the format used by the binary storage mechanism allows recursive and cyclic objects to be handled correctly, which is not possible with the ascii representation used by the normal storeOn: mechanism.

Non binary store

In smalltalk, all classes support the storeOn: message, which asks the object to append a textual (i.e. ascii) representation of itself to a stream, from which this object can be reconstructed.
This scheme works for simple objects, which do NOT contain self references or cycles.
Also, this format is compatible between different smalltalk implementations, if the layout of the instances is the same across them.
For example:
	|myObject outStream|

	myObject := Array with:'hello world'
			  with:1.2345
			  with:#(1 2 3 4 5)
			  with:('one' -> #one).

	outStream := 'data' asFilename writeStream.

	myObject storeOn:outStream.

	outStream close.
stores the array (myObject) in the file named "data".

If you inspect this file, you will notice that it simply contains a smalltalk expression which recreates the original array. Thus, the object can be read back by asking the compiler to evaluate that expression:

	|string|
        
	string := 'data' asFilename readStream contents.

	myObject := Compiler evaluate:string
the above has been wrapped into an easier to use method, which is understood by any class:
	|inStream|

	inStream := 'data' asFileName readStream.
	myObject := Object readFrom:inStream.
Thus, any object can be stored by sending it storeOn: and retrieved by sending readFrom: to Object or a class.

Problems with non binary store

Although simple and straitforward, this mechanism has a few drawbacks:

Using binary storage

Binary storage solves the above problems, by storing objects in an encoded format, keeping track of object identity.
In contrast to the above described storeOn: format, this format is not meant to be human readable. Also, since it uses all 8bits of a byte, it may not be possible to send binary encoded objects via some transport mechanisms (i.e. electronic mail without using some uu-encoding).

Binary storage has the disadvantage that it is not compatible between different smalltalk implementations (since no common standard exists yet).

Use is pretty much the same as above:

	|a1 a2 hello inStream outStream|

	hello := 'hello'.
	a1 := Array with:hello
		    with:hello
		    with:'hello'

	outStream := 'data' asFilename writeStream.
	a1 storeBinaryOn:outStream.
	outStream close.

	inStream := 'data' asFileName readStream.
	a2 := Object readBinaryFrom:inStream.
	inStream close.

	(a1 at:1) == (a1 at:2)  "evaluates to true"

	(a2 at:1) == (a2 at:2)  "evaluates to true"
The above can be used on any stream which supports reading/writing of bytes. (i.e. FileStreams, WriteStream on a ByteArray, Sockets, Pipes etc).

Storing objects in a simple database

description of PersistencyManager to be added here

Error handling

Binary storage is much more sensitive to changed instance layout (of classes) than textual storage. Consider the following case:
  1. an object is stored somewhere
  2. the objects class is changed to include one more instance variable
  3. you try to load the original object
Of course, at retrieval time, the now existing class is no longer valid for the object to be reconstructed.

Smalltalk/X offers an error handling mechanism to catch cases when an object is restored for which no valid class exists. The error is signalled using the exception mechanism (see Exception handling and signals).

It is possible to catch the error and either:

Ignoring errors

All errors are signalled by one of the signals: By defining a handler for these, a binary read operation will not be aborted, but instead let the exception handler decide what to do; if it proceeds, the binaryLoad is not aborted. Instead, a dummy class will be created for the restored object (a subclass of ObsoleteObject and the loaded object is made an instance of it.

See example code in "doc/coding/BOSS-errors"

Correcting errors

It is possible to convert obsolete objects to another format or to becoming an instance of another class while reading binary data.

To do so, the binaryLoader will raise the requestConversion exception, passing the existing class and the obsolete object as arguments. The handler should somehow try to convert the obsolete object and proceed with the new object as value.

The existing class can provide a conversion method (cloneFrom:), which has to create and return an instance of itself given an obsolete object as a template. A default cloneFrom: method is provided, which creates an object with all named and indexed instance variables preserved. However, for special needs, your class may redefine this method and do whatever is required for conversion (or even decide to return nil ...)

See example code in "doc/coding/BOSS-errors"


Copyright © Claus Gittinger Development & Consulting, all rights reserved

(cg@ssw.de)