Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 41 - Serialisierung

## 41.3 Anwendungen

- 41.3 Anwendungen
  - 41.3.1 Ein einfacher Objektspeicher
  - 41.3.2 Kopieren von Objekten

### 41.3.1 Ein einfacher Objektspeicher

Die folgende Klasse `TrivialObjectStore` stellt ein einfaches Beispiel für einen persistenten Objektspeicher dar. Sie besitzt eine Methode `putObject`, mit der beliebige `String`-`Object`-Paare angelegt und später mit `getObject` wieder abgerufen werden können. Durch Aufruf von `save` kann der komplette Objektspeicher serialisiert werden, ein Aufruf von `load` lädt ihn von der Festplatte. Die Klasse `TrivialObjectStore` verwendet eine `Hashtable` zur Ablage der `String`-`Object`-Paare. Diese implementiert bereits standardmäßig das Interface `Serializable` und kann daher sehr einfach auf einem externen Datenträger gespeichert oder von dort geladen werden.

    001 /* TrivialObjectStore.java */
    002
    003 import java.io.*;
    004 import java.util.*;
    005
    006 /**
    007  * Trivialer Objektspeicher, der Mengen von Name-Objekt-
    008  * Paaren aufnehmen und persistent speichern kann.
    009  */
    010 public class TrivialObjectStore
    011 {
    012   //Instance variables
    013   private String fname;
    014   private Hashtable objects;
    015
    016   /**
    017    * Erzeugt einen neuen Objektspeicher mit dem angegebenen
    018    * Namen (die Erweiterung ".tos" ("trivial object store")
    019    * wird ggfs. automatisch angehängt.
    020    */
    021   public TrivialObjectStore(String fname)
    022   {
    023     this.fname = fname;
    024     if (!fname.endsWith(".tos")) {
    025       this.fname += ".tos";
    026     }
    027     this.objects = new Hashtable(50);
    028   }
    029
    030   /**
    031    * Sichert den Objektspeicher unter dem im Konstruktor
    032    * angegebenen Namen.
    033    */
    034   public void save()
    035   throws IOException
    036   {
    037     FileOutputStream fs = new FileOutputStream(fname);
    038     ObjectOutputStream os = new ObjectOutputStream(fs);
    039     os.writeObject(objects);
    040     os.close();
    041   }
    042
    043   /**
    044    * Lädt den Objektspeicher mit dem im Konstruktor
    045    * angegebenen Namen.
    046    */
    047   public void load()
    048   throws ClassNotFoundException, IOException
    049   {
    050     FileInputStream fs = new FileInputStream(fname);
    051     ObjectInputStream is = new ObjectInputStream(fs);
    052     objects = (Hashtable)is.readObject();
    053     is.close();
    054   }
    055
    056   /**
    057    * Fügt ein Objekt in den Objektspeicher ein.
    058    */
    059   public void putObject(String name, Object object)
    060   {
    061     objects.put(name, object);
    062   }
    063
    064   /**
    065    * Liest das Objekt mit dem angegebenen Namen aus dem
    066    * Objektspeicher. Ist es nicht vorhanden, wird null
    067    * zurückgegeben.
    068    */
    069   public Object getObject(String name)
    070   {
    071     return objects.get(name);
    072   }
    073
    074   /**
    075    * Liefert eine Aufzählung aller gespeicherten Namen.
    076    */
    077   public Enumeration getAllNames()
    078   {
    079     return objects.keys();
    080   }
    081 }
| TrivialObjectStore.java

Listing 41.9: Ein einfacher Objektspeicher

Objekte der Klasse `TrivialObjectStore` können nun verwendet werden, um beliebige serialisierbare Objekte unter Zuordnung eines Namens auf einem externen Datenträger zu speichern. Das folgende Listing zeigt dies am Beispiel eines fiktiven »Tamagotchi-Shops«, dessen Eigenschaften Name, Besitzer und Liste der Produkte im Objektspeicher abgelegt, in die Datei shop.tos geschrieben und anschließend wieder ausgelesen werden:

    001 /* Listing4110.java */
    002
    003 import java.io.*;
    004 import java.util.*;
    005
    006 public class Listing4110
    007 {
    008   public static void main(String[] args)
    009   {
    010     //Erzeugen und Speichern des Objektspeichers
    011     TrivialObjectStore tos = new TrivialObjectStore("shop");
    012     tos.putObject("name", "Tami-Shop Norderelbe");
    013     tos.putObject("besitzer", "Meier, Fridolin");
    014     Vector products = new Vector(10);
    015     products.addElement("Dinky Dino");
    016     products.addElement("96er Classic");
    017     products.addElement("Black Frog");
    018     products.addElement("SmartGotchi");
    019     products.addElement("Pretty Dolly");
    020     tos.putObject("produkte", products);
    021     try {
    022       tos.save();
    023     } catch (IOException e) {
    024       System.err.println(e.toString());
    025     }
    026
    027     //Einlesen des Objektspeichers
    028     TrivialObjectStore tos2 = new TrivialObjectStore("shop");
    029     try {
    030       tos2.load();
    031       Enumeration names = tos2.getAllNames();
    032       while (names.hasMoreElements()) {
    033         String name = (String)names.nextElement();
    034         Object obj = tos2.getObject(name);
    035         System.out.print(name + ": ");
    036         System.out.println(obj.getClass().toString());
    037         if (obj instanceof Collection) {
    038           Iterator it = ((Collection)obj).iterator();
    039           while (it.hasNext()) {
    040             System.out.println("  " + it.next().toString());
    041           }
    042         } else {
    043           System.out.println("  " + obj.toString());
    044         }
    045       }
    046     } catch (IOException e) {
    047       System.err.println(e.toString());
    048     } catch (ClassNotFoundException e) {
    049       System.err.println(e.toString());
    050     }
    051   }
    052 }
| Listing4110.java

Listing 41.10: Beispielanwendung für den einfachen Objektspeicher

Hier wird zunächst ein neuer Objektspeicher `tos` erstellt und mit den Objekten aus dem Tamagotchi-Shop gefüllt. Neben zwei Strings `name` und `besitzer` wird dabei unter der Bezeichnung `produkte` eine weitere Collection, der `Vector` mit den Produkten, eingefügt. Das durch Aufruf von `save` ausgelöste Serialisieren der `Hashtable` bewirkt, daß alle darin gespeicherten Elemente serialisiert werden, sofern sie das Interface `Serializable` implementieren.

Ab Zeile 027 wird dann der Objektspeicher wieder eingelesen, in diesem Fall in die Variable `tos2`. Mit `getAllNames` beschafft das Programm zunächst eine `Enumeration` über alle Objektnamen und durchläuft sie elementweise. Zu jedem Namen wird mit `getElement` das zugehörige Element geholt, und sein Name und der Name der zugehörigen Klasse werden ausgegeben (Zeile 036). Anschließend wird überprüft, ob das gefundene Objekt das Interface `Collection` implementiert und ggfs. über alle darin enthaltenen Elemente iteriert. Andernfalls wird das Objekt direkt mit `toString` ausgegeben.

Die Ausgabe des Programms ist:

    produkte: class java.util.Vector
      Dinky Dino
      96er Classic
      Black Frog
      SmartGotchi
      Pretty Dolly
    besitzer: class java.lang.String
      Meier, Fridolin
    name: class java.lang.String
      Tami-Shop Norderelbe

|  | Die Klasse `TrivialObjectStore` verdeutlicht eine mögliche Vorgehensweise bei der persistenten Speicherung von Objekten. Für einen echten Praxiseinsatz (etwa in der Anwendungsentwicklung) fehlen aber noch ein paar wichtige Eigenschaften: Anstatt den Objektspeicher immer komplett zu laden und zu speichern, sollte es möglich sein, einzelne Elemente zu speichern, zu laden und zu löschen. Der Objektspeicher sollte mehrbenutzerfähig sein und Transaktions- und Recovery-Logik mitbringen. Die Suche nach Objekten sollte durch Indexdateien beschleunigt werden können. |  |
Hinweis

Leider ist die Implementierung dieser Features nicht trivial. Ein gutes Beispiel für die Implementierung des ersten und dritten Punkts findet sich in "Java Algorithms" von Scott Robert Ladd. Der Autor zeigt zunächst, wie man Objekte auf der Basis von Random-Access-Dateien (anstelle der üblichen Streams) serialisiert. Anschließend zeigt er die Verwendung von Indexdateien am Beispiel der Implementierung von B-Trees.

### 41.3.2 Kopieren von Objekten

Eine auf den ersten Blick überraschende Anwendung der Serialisierung besteht darin, Objekte zu kopieren. Es sei noch einmal daran erinnert, daß die Zuweisung eines Objektes an eine Objektvariable lediglich eine Zeigeroperation war; daß also immer nur ein Verweis geändert wurde. Soll ein komplexes Objekt kopiert werden, wird dazu üblicherweise das Interface `Cloneable` implementiert und eine Methode `clone` zur Verfügung gestellt, die den eigentlichen Kopiervorgang vornimmt. Sollte ein Objekt kopiert werden, das `Cloneable` nicht implementiert, blieb bisher nur der umständliche Weg über das manuelle Kopieren aller Membervariablen. Das ist insbesondere dann mühsam und fehlerträchtig, wenn das zu kopierende Objekt Unterobjekte enthält, die ihrerseits kopiert werden müssen (deep copy anstatt shallow copy).

Der Schlüssel zum Kopieren von Objekten mit Hilfe der Serialisierung liegt darin, anstelle der üblichen dateibasierten Streamklassen solche zu verwenden, die ihre Daten im Hauptspeicher halten. Am besten sind dazu `ByteArrayOutputStream` und `ByteArrayInputStream` geeignet. Sie sind integraler Bestandteil der `OutputStream`- und `InputStream`-Hierarchien und man kann die Daten problemlos von einem zum anderen übergeben. Das folgende Programm implementiert eine Methode `seriaClone()`, die ein beliebiges Objekt als Argument erwartet und in einen `ByteArrayOutputStream` serialisiert. Das resultierende Byte-Array wird dann zur Konstruktion eines `ByteArrayInputStream` verwendet, dort deserialisiert und als Objektkopie an den Aufrufer zurückgegeben:

    001 /* Listing4111.java */
    002
    003 import java.io.*;
    004 import java.util.*;
    005
    006 public class Listing4111
    007 {
    008   public static Object seriaClone(Object o)
    009   throws IOException, ClassNotFoundException
    010   {
    011     //Serialisieren des Objekts
    012     ByteArrayOutputStream out = new ByteArrayOutputStream();
    013     ObjectOutputStream os = new ObjectOutputStream(out);
    014     os.writeObject(o);
    015     os.flush();
    016     //Deserialisieren des Objekts
    017     ByteArrayInputStream in = new ByteArrayInputStream(
    018       out.toByteArray()
    019     );
    020     ObjectInputStream is = new ObjectInputStream(in);
    021     Object ret = is.readObject();
    022     is.close();
    023     os.close();
    024     return ret;
    025   }
    026
    027   public static void main(String[] args)
    028   {
    029     try {
    030       //Erzeugen des Buchobjekts
    031       Book book = new Book();
    032       book.author = "Peitgen, Heinz-Otto";
    033       String[] s = {"Jürgens, Hartmut", "Saupe, Dietmar"};
    034       book.coAuthors = s;
    035       book.title = "Bausteine des Chaos";
    036       book.publisher = "rororo science";
    037       book.pubyear = 1998;
    038       book.pages = 514;
    039       book.isbn = "3-499-60250-4";
    040       book.reflist = new Vector();
    041       book.reflist.addElement("The World of MC Escher");
    042       book.reflist.addElement(
    043         "Die fraktale Geometrie der Natur"
    044       );
    045       book.reflist.addElement("Gödel, Escher, Bach");
    046       System.out.println(book.toString());
    047       //Erzeugen und Verändern der Kopie
    048       Book copy = (Book)seriaClone(book);
    049       copy.title += " - Fraktale";
    050       copy.reflist.addElement("Fractal Creations");
    051       //Ausgeben von Original und Kopie
    052       System.out.print(book.toString());
    053       System.out.println("---");
    054       System.out.print(copy.toString());
    055     } catch (IOException e) {
    056       System.err.println(e.toString());
    057     } catch (ClassNotFoundException e) {
    058       System.err.println(e.toString());
    059     }
    060   }
    061 }
    062
    063 class Book
    064 implements Serializable
    065 {
    066   public String author;
    067   public String[] coAuthors;
    068   public String title;
    069   public String publisher;
    070   public int    pubyear;
    071   public int    pages;
    072   public String isbn;
    073   public Vector reflist;
    074
    075   public String toString()
    076   {
    077     String NL = System.getProperty("line.separator");
    078     StringBuffer ret = new StringBuffer(200);
    079     ret.append(author + NL);
    080     for (int i = 0; i < coAuthors.length; ++i) {
    081       ret.append(coAuthors[i] + NL);
    082     }
    083     ret.append("\"" + title + "\"" + NL);
    084     ret.append(publisher + " " + pubyear + NL);
    085     ret.append(pages + " pages" + NL);
    086     ret.append(isbn + NL);
    087     Enumeration e = reflist.elements();
    088     while (e.hasMoreElements()) {
    089       ret.append("  " + (String)e.nextElement() + NL);
    090     }
    091     return ret.toString();
    092   }
    093 }
| Listing4111.java

Listing 41.11: Kopieren von Objekten durch Serialisierung

Das Programm verwendet zum Testen ein Objekt der Klasse `Book`, das mit den Daten eines Buchtitels initialisiert wird. Anschließend wird mit `seriaClone` eine Kopie hergestellt und der Variable `copy` zugewiesen. Um zu verdeutlichen, daß wirklich eine Kopie hergestellt wurde, modifizieren wir nun einige Angaben der Kopie und geben anschließend beide Objekte aus:

    Peitgen, Heinz-Otto
    Jürgens, Hartmut
    Saupe, Dietmar
    "Bausteine des Chaos"
    rororo science 1998
    514 pages
    3-499-60250-4
      The World of MC Escher
      Die fraktale Geometrie der Natur
      Gödel, Escher, Bach
    ---
    Peitgen, Heinz-Otto
    Jürgens, Hartmut
    Saupe, Dietmar
    "Bausteine des Chaos - Fraktale"
    rororo science 1998
    514 pages
    3-499-60250-4
      The World of MC Escher
      Die fraktale Geometrie der Natur
      Gödel, Escher, Bach
      Fractal Creations

An der Programmausgabe kann man erkennen, daß das Objekt tatsächlich ordnungsgemäß kopiert wurde. Auch alle Unterobjekte wurden kopiert und konnten anschließend unabhängig voneinander geändert werden. Ohne Serialisierung wäre der manuelle Aufwand um ein Vielfaches größer gewesen. Das Verfahren findet dort seine Grenzen, wo die zu kopierenden Objekte nicht serialisierbar sind oder nicht-serialisierbare Unterobjekte enthalten. Zudem muß im echten Einsatz das Laufzeitverhalten überprüft werden, denn der Vorgang des Serialisierens/Deserialisierens ist um ein Vielfaches langsamer als das direkte Kopieren der Objektattribute.

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de