Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 42 - Serialisierung

42.3 Anwendungen



42.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<String, Object> 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<String, Object>(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<String, Object>)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<String> getAllNames()
078   {
079     return objects.keys();
080   }
081 }
TrivialObjectStore.java
Listing 42.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 /* Listing4210.java */
002 
003 import java.io.*;
004 import java.util.*;
005 
006 public class Listing4210
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<String> products = new Vector<String>(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<String> names = tos2.getAllNames();
032       while (names.hasMoreElements()) {
033         String name = 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 }
Listing4210.java
Listing 42.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, dass 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.

42.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, dass die Zuweisung eines Objekts an eine Objektvariable lediglich eine Zeigeroperation war; dass 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 /* Listing4211.java */
002 
003 import java.io.*;
004 import java.util.*;
005 
006 public class Listing4211
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<String>();
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<String> 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<String> e = reflist.elements();
088     while (e.hasMoreElements()) {
089       ret.append("  " + e.nextElement() + NL);
090     }
091     return ret.toString();
092   }
093 }
Listing4211.java
Listing 42.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, dass 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, dass 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 nichtserialisierbare Unterobjekte enthalten.

Zudem muss 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.

 Warnung 


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0
 <<    <     >    >>   API  © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de