Streams, Serialisierung und Datei E/A

Bei einem Objekt handelt es sich um einen realen Gegenstand oder um Konstrukte unseres Denkens. Diese sind mit Eigenschaften, den sogenannten Attributen und mit Funktionen, den sogenannten Methoden, ausgestattet. Objekte haben einen Zustand und ein Verhalten. Die Klasse dient als Bauplan für die Abbildung von realen Objekten in Softwareobjekte und beschreibt Attribute (Eigenschaften) und Methoden (Verhaltensweisen) der Objekte. Verallgemeinernd könnte man auch sagen, dass eine Klasse dem Datentyp eines Objekts entspricht. Ein Objekt befindet sich zu jedem Zeitpunkt in einem bestimmten Zustand, der von den Attributen und Methoden abhängt und verändert werden kann. Um den Zustand eines Objektes zu einem bestimmten Zeitpunkt speichern und später wieder aufrufen zu können, gibt es zwei verschiedene Methoden.

Warum möchte ich überhaupt Zustände speichern? In der Vorlesung haben wir uns an dem Beispiel eines Computerspieles bedient. Ziel unseres Computerspiels ist es, ein Haus einzurichten. Wir kaufen ein Bett, eine Küche und einen Kleiderschrank. Nachdem wir nun schon eine Weile gespielt haben und müde sind beschließen wir, das Spiel morgen weiterzuspielen. Natürlich gehen wir nun davon aus, dass der aktuelle Zustand (des Hauses inklusive Bett, Küche und Kleiderschrank) morgen ebenfalls noch erhalten ist. Doch können wir darauf vertrauen und wie funktioniert das?


Serialisierung (Speicherung)

Werden die Daten ausschließlich mit demselben Java-Programm wiederverwendet, mit dem sie erzeugt werden, kommt die Methode der Serialisierung in Frage.

Die Serialisierung ist ein Mechanismus, bei dem Objekte in eine Folge von Bytes verwandelt und umgekehrt daraus wieder Objekte erzeugt werden können (Deserialisierung). Dadurch können Objekte mit ihrem derzeitigen Zustand in einer Datenbank gespeichert und später wieder genauso aufgerufen werden, wie sie zuletzt verwendet wurden.

Stack („Stapel“) und Heap („Haufen“) bezeichnen Datenstrukturen mit ihren ganz speziellen Eigenschaften. Im Kontext der Speicherverwaltung auf Programmebene handelt es sich also sowohl beim Stack als auch beim Heap um Teile des Arbeitsspeichers, die vom Betriebssystem dem ausführenden Programm zur Verfügung gestellt werden. Der Name „Stack“ deutet an, dass die Daten hier „aufeinander“ liegen. Damit ist gemeint, dass neue Daten immer nur oben draufgelegt werden können. Der Heap ist nicht so strukturiert wie der Stack. Du kannst ihn dir tatsächlich als Haufen vorstellen, auf dem jede Menge Platz ist. Während der Stack nämlich von der Größe her stark begrenzt ist, kann der Heap anwachsen bis die Speichergrenze auf Prozessebene erreicht ist.

Bei der Serialisierung werden die Werte der Instanzvariablen eines Objekts auf dem Heap abgespeichert, sodass zu einem späteren Zeitpunkt wieder ein identisches Objekt auf dem Heap erzeugt werden kann.

Vorgang der Serialisierung

Um ein Objekt serialisieren zu können, muss im ersten Schritt ein Import des java.io Pakets vorgenommen werden. Erst daraufhin kann das Interface Serializable implementiert werden, das gewährleistet, dass die Objekte der Klasse serialisiert werden dürfen. Dabei enthält Serializable keine zu implementierenden Methoden und hat sozusagen nur eine Markierungsfunktion. Ist eine Superklasse mit Serializable markiert, gilt dies auch für alle Objekte der Unterklassen.

Um die Speicherung eines Objektes erreichen zu können, werden zwei Objekte aus dem Paket java.io  benötigt: ein FileOutputStream und einen ObjektOutputStream.

FileOutputStream fileStream = new FileOutputStream(“MeinSpiel.ser”);

ObjectOutputStream os = new ObjectOutputStream(fileStream);

Dabei stellt der FileOutputStream einen Anschlussstrom dar, der die Verbindung zum Zielort, also in dem Fall dem Speicherort herstellt. Er kann ein Objekt in Daten umwandeln, die in einen Strom geschickt werden können. Weiterhin bedarf es dann dem ObjectOutputStream, der einen Verkettungsstrom darstellt. Das bedeutet, dass hiermit ein Objekt geschrieben werden kann, das allerdings nicht direkt mit einer Datei verknüpft wird. Durch die Kombination beider wird im ersten Schritt mithilfe des ObjectOutputStream ein Objekt geschrieben, in Daten umgewandelt und weitergeschickt. Im zweiten Schritt gelangt das Objekt dann über den Strom zum FileOutputStream, welcher eine Verbindung zum Zielort herstellt und die Daten in Form von Bytes in eine Datei schreibt.

Nach der Instanziierung der beiden Streams kann folgend das Objekt geschrieben werden:

os.writeObject(figur1);
os.writeObject(figur2);
os.writeObject(figur3);

Es können entweder primitive Werte direkt gespeichert werden oder aber Werte einer Instanzvariablen eines Objekts. Hierzu wird kein zusätzlicher Code benötigt, denn wenn ein Objekt serialisiert wird, passiert dies folgend auch mit allen weiteren Objekten, die es über Instanzvariablen referenziert. Abschließend wird die close() Methode benutzt, um den ObjektOutputStream zu schließen. Die darunter liegenden Ströme werden automatisch auch geschlossen. 

os.close();


Deserialisierung

Ziel der Deserialisierung ist es, den Zustand des Objektes wieder herstellen zu können. Dazu wird ein neues Objekt auf dem Heap erzeugt, welches den gleichen Zustand hat wie das Objekt vor der Serialisierung.

Vorgang der Deserilaisierung

Um ein Objekt nun wiederherstellen zu können, werden die beiden Objekte FileInputStream und ObjectInputStream benötigt.

FileInputStream fileStream = new FileInputStream(„MeinSpiel.ser”);

ObjectInputStream os = new ObjectInputStream(fileStream);

Der Vorgang entspricht einer umgekehrten Serialisierung. Im ersten Schritt wird die gespeicherte Datei vom FileInputStream gelesen und die Bytes in Daten zurückverwandelt. Dann erfolgt eine Verkettung zum ObjectInputStream der diese Daten dann wieder zurück in die Klasse schreibt und dem Objekt seine Instanzvariablen zuweist.

Nachdem diese beiden Objekte instanziiert wurden, wird folgend für jedes Objekt aus dem Strom die readObject() Methode aufgerufen und es werden die Objekte in der selben Reihenfolge ausgelesen, wie sie auch gespeichert wurden.

Object eins = os.readObject();
Object zwei = os.readObject();
Object drei = os.readObject();

Zusätzlich zum Auslesen der Objekte bedarf es hier noch jeweils einem Cast für jedes Objekt, da der Rückgabewert der readObject() Methode vom Typ Object ist.

Spielfigur elb = (Spielfigur) eins;
Spielfigur troll = (Spielfigur) zwei;
Spielfigur zauberer = (Spielfigur) drei;

Hier müssen die Objekte dann schließlich manuell in den Typ umgewandelt werden, dem sie auch vorher entsprochen haben. Abschließend wird die close() Methode benutzt, um den ObjektInputStream zu schließen. Die darunter liegenden Ströme werden automatisch auch geschlossen. 

os.close();


Datei E/A (Eingabe/Ausgabe)

Das Speichern durch Serialisierung ist der einfache Weg. Die andere Möglichkeit ist jedoch das Schreiben einer einfachen Textdatei. Diese Textdatei kann ebenfalls von einem Nicht-Java-Programm gelesen werden. Das Schreiben von Textdateien ähnelt dem Schreiben eines Objektes, abgesehen davon, dass statt eines Objektes und einem FileOutputStream, ein String und ein FileWriter verwendet wird.

Dabei werden die einzelnen Bestandteile des Zustands durch Kommatrennung in abgetrennte Zeilen in eine Textdatei getippt. Beispielsweise enthält die Klasse Spielfigur die Attribute: Stärke, Typ und Waffe. In die Textdatei schreibe ich nun folgendes:

20,Fee,Zauberstab,Unsichtbarkeit,Staub

Für den FileWriter brauche ich ebenfalls das java.io Paket. Ich schreibe meine FileWriter() Methode nun in einem try/catch-Block. In diesem Block tippe ich nun meine exisitierende Datei ein, oder eine Datei mit dem entsprechenden Namen wird erzeugt. Die Methode write() nimmt einen String entgegen. Die oben aufgelisteten Elemente der Textdateien werden nun in die write() Methode geschrieben. Diese kommen gemeinsam mit anderen Strings in einen sogenannten „Puffer“. Alle Strings werden in den FileWriter als Zeichen geschrieben. Abschließend arbeiten wir wieder mit der close() Methode.

Zum Lesen der Textdatei benutzt wir die FileReader() und die BufferedReader() Methode. Ein FileReader ist ein Anschlussstrom für Zeichen, der sich mit einer Textdatei verbindet und das eigentliche Lesen erledigt. Die BufferedReader Methode macht das Lesen effizienter. Er kehrt erst dann wieder zum Lesen zur Datei zurück, wenn der Puffer leer ist, also das Programm alles daraus gelesen hat.

Hier gehts weiter zum Thema Netzwerkprogrammierung und Threads.

Erstelle eine Website wie diese mit WordPress.com
Jetzt starten