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