JavaRush /Java-Blog /Random-DE /Serialisierung und Deserialisierung in Java

Serialisierung und Deserialisierung in Java

Veröffentlicht in der Gruppe Random-DE
Hallo! In der heutigen Vorlesung werden wir über Serialisierung und Deserialisierung in Java sprechen. Beginnen wir mit einem einfachen Beispiel. Nehmen wir an, Sie sind der Schöpfer eines Computerspiels. Wenn Sie in den 90er Jahren aufgewachsen sind und sich an die damaligen Spielekonsolen erinnern, wissen Sie wahrscheinlich, dass ihnen heute etwas Offensichtliches fehlte – das Speichern und Laden von Spielen :) Wenn nicht ... stellen Sie sich vor! Ich befürchte, dass heute ein Spiel ohne eine solche Option zum Scheitern verurteilt sein wird! Und was bedeutet eigentlich „Speichern“ und „Laden“ eines Spiels? Nun, im üblichen Sinne verstehen wir, worum es geht: Wir wollen das Spiel dort fortsetzen, wo wir beim letzten Mal aufgehört haben. Dazu erstellen wir eine Art „Checkpoint“, über den wir dann das Spiel laden. Aber was bedeutet das, nicht im alltäglichen Sinne, sondern im „Programmierer“-Sinn? Die Antwort ist einfach: Wir speichern den Status unseres Programms. Nehmen wir an, Sie spielen ein Strategiespiel für Spanien. Ihr Spiel hat einen Zustand: Wem gehören welche Gebiete, wer hat wie viele Ressourcen, wer ist mit wem im Bündnis und wer befindet sich im Gegenteil im Krieg und so weiter. Diese Informationen, der Status unseres Programms, müssen irgendwie gespeichert werden, um die Daten später wiederherzustellen und das Spiel fortzusetzen. Genau hierfür werden die Mechanismen der Serialisierung und Deserialisierung genutzt . Bei der Serialisierung wird der Zustand eines Objekts in einer Bytefolge gespeichert. Unter Deserialisierung versteht man den Prozess der Rekonstruktion eines Objekts aus diesen Bytes. Jedes Java-Objekt wird in eine Folge von Bytes konvertiert. Wofür ist das? Wir haben mehr als einmal gesagt, dass Programme nicht für sich allein existieren. Am häufigsten interagieren sie miteinander, tauschen Daten aus usw. Und das Byte-Format ist dafür praktisch und effizient. Wir können zum Beispiel unser Klassenobjekt SavedGame(ein gespeichertes Spiel) in eine Folge von Bytes umwandeln, diese Bytes über das Netzwerk an einen anderen Computer übertragen und diese Bytes auf dem zweiten Computer wieder in ein Java-Objekt umwandeln! Es ist schwer zu hören, oder? Anscheinend wird es nicht einfach sein, diesen Prozess zu organisieren :/ Zum Glück nein! :) In Java ist die Serializable- Schnittstelle für Serialisierungsprozesse verantwortlich . Diese Schnittstelle ist äußerst einfach: Sie müssen keine einzige Methode implementieren, um sie zu verwenden! So wird unsere Save-Game-Klasse aussehen:
import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private static final long serialVersionUID = 1L;

   private String[] territoriesInfo;
   private String[] resourcesInfo;
   private String[] diplomacyInfo;

   public SavedGame(String[] territoriesInfo, String[] resourcesInfo, String[] diplomacyInfo){
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public String[] getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(String[] territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public String[] getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(String[] resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public String[] getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(String[] diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + Arrays.toString(territoriesInfo) +
               ", resourcesInfo=" + Arrays.toString(resourcesInfo) +
               ", diplomacyInfo=" + Arrays.toString(diplomacyInfo) +
               '}';
   }
}
Drei Datensätze sind für Informationen über Territorien, Wirtschaft und Diplomatie verantwortlich, und die Serializable-Schnittstelle teilt der Java-Maschine mit: „ Alles ist in Ordnung, wenn überhaupt, können Objekte dieser Klasse serialisiert werden .“ Eine Schnittstelle, die keine Methoden hat, sieht seltsam aus :/ Warum wird sie benötigt? Die Antwort auf diese Frage lautet oben: Nur um der Java-Maschine die erforderlichen Informationen bereitzustellen. In einer der vorherigen Vorlesungen haben wir Marker-Schnittstellen kurz erwähnt. Hierbei handelt es sich um spezielle Informationsschnittstellen, die unsere Klassen lediglich mit zusätzlichen Informationen markieren, die für die Java-Maschine in Zukunft nützlich sein werden. Sie verfügen über keine Methoden, die implementiert werden müssen. Serializable ist also eine dieser Schnittstellen. Ein weiterer wichtiger Punkt: die Variable, private static final long serialVersionUIDdie wir in der Klasse definiert haben. Warum wird es benötigt? Dieses Feld enthält die eindeutige Versionskennung der serialisierten Klasse . Jede Klasse, die die Serializable-Schnittstelle implementiert, verfügt über eine Versionskennung. Sie wird basierend auf dem Inhalt der Klasse berechnet – Felder, Deklarationsreihenfolge, Methoden. Und wenn wir den Feldtyp und/oder die Anzahl der Felder in unserer Klasse ändern, ändert sich sofort die Versionskennung. serialVersionUID wird auch geschrieben, wenn die Klasse serialisiert wird. Wenn wir versuchen, ein Objekt aus einer Reihe von Bytes zu deserialisieren, also wiederherzustellen, wird der Wert mit dem Wert der Klasse in unserem Programm serialVersionUIDverglichen . serialVersionUIDWenn die Werte nicht übereinstimmen, wird eine java.io.InvalidClassException geworfen. Ein Beispiel dafür sehen wir weiter unten. Um solche Situationen zu vermeiden, legen wir diese Versions-ID einfach manuell für unsere Klasse fest. In unserem Fall ist es einfach gleich 1 (Sie können es durch eine beliebige andere Zahl ersetzen). Nun ist es an der Zeit, unser Objekt zu serialisieren SavedGameund zu sehen, was passiert!
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // unser Objekt erstellen
       String[] territoryInfo = {„Spanien hat 6 Provinzen“, „Russland hat 10 Provinzen“, „Frankreich hat 8 Provinzen“};
       String[] resourcesInfo = {„Spanien hat 100 Gold“, „Russland hat 80 Gold“, „Frankreich hat 90 Gold“};
       String[] diplomacyInfo = {„Frankreich befindet sich im Krieg mit Russland, Spanien hat eine Neutralitätsposition eingenommen“};

       SavedGame savedGame = new SavedGame(territoryInfo, resourcesInfo, diplomacyInfo);

       //2 Threads erstellen, um das Objekt zu serialisieren und in einer Datei zu speichern
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Spiel in Datei speichern
       objectOutputStream.writeObject(savedGame);

       // Den Stream schließen und Ressourcen freigeben
       objectOutputStream.close();
   }
}
Wie Sie sehen, haben wir zwei Threads erstellt – FileOutputStreamund ObjectOutputStream. Der erste kann Daten in eine Datei schreiben und der zweite kann Objekte in Bytes umwandeln. Ähnliche „verschachtelte“ Konstruktionen haben Sie beispielsweise bereits new BufferedReader(new InputStreamReader(...))in früheren Vorlesungen gesehen, sie sollten Sie also nicht abschrecken :) Indem wir eine solche „Kette“ aus zwei Threads erstellen, führen wir beide Aufgaben aus: Wir verwandeln das Objekt SavedGamein eine Menge von Bytes und speichern Sie es mit der Methode in einer Datei writeObject(). Und übrigens haben wir nicht einmal nachgeschaut, was wir bekommen haben! Es ist Zeit, sich die Datei anzusehen! *Hinweis: Die Datei muss nicht im Voraus erstellt werden. Wenn eine Datei mit diesem Namen nicht existiert, wird sie automatisch erstellt* Und hier ist ihr Inhalt! ¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ TerritoryInfoq ~ xpur [Ljava.lang.String;ТVзй{G xp t pФранцвоюет SЃ R оссией, Р˜ С ЃРїР° РЅРёСЏ Р·Р°Ряла позицию нейтралитетаuq ~ t "РЈ Р˜СЃРїР°РЅ РёРё 100 золотаt Р J R РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Р˜СЃРїР° РЅРёРё 6 РїСЂРѕРІРёРЅС †РёР№t %РЈ Р РѕСЃСЃРёРё 10 РїСЂРѕРІРёРЅС †РёР№t &РЈ ФранцРеРё 8 проввинций Ups :( Es scheint, dass unser Programm nicht funktioniert hat :( Tatsächlich hat es funktioniert. Sie erinnern sich, dass wir genau einen Satz Bytes in die Datei übertragen haben, und nicht nur ein Objekt oder Text? Nun, so sieht dieses Set aus :) Das ist unser gespeichertes Spiel! Wenn wir unser ursprüngliches Objekt wiederherstellen wollen, also das Spiel dort laden und fortsetzen wollen, wo wir aufgehört haben, brauchen wir das umgekehrter Prozess, Deserialisierung ... So wird es in unserem Fall aussehen:
import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);
   }
}
Und hier ist das Ergebnis! SavedGame{territoriesInfo=[Spanien hat 6 Provinzen, Russland hat 10 Provinzen, Frankreich hat 8 Provinzen], resourcesInfo=[Spanien hat 100 Gold, Russland hat 80 Gold, Frankreich hat 90 Gold], diplomacyInfo=[Frankreich befindet sich im Krieg mit Russland, Spanien hat die Neutralitätsposition eingenommen]} Großartig! Wir haben es geschafft, den Status unseres Spiels zunächst in einer Datei zu speichern und ihn dann aus der Datei wiederherzustellen. Versuchen wir nun, dasselbe zu tun, entfernen jedoch SavedGamedie Versionskennung aus unserer Klasse. Wir werden nicht beide Klassen neu schreiben, der Code darin wird derselbe sein, wir werden nur SavedGameentfernen private static final long serialVersionUID. Hier ist unser Objekt nach der Serialisierung: ¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ TerritorienInfoq ~ xpur [Ljava.lang.String;ТВзй{G xp t pФранция РІРѕСЋРµС ‚ СЃ Россией, Р˜СЃРїР°РЅРЏР·Р°РЅСЏР»Р° RїРѕР·РёС†РёСЋ нейтрал итетаuq ~ t "РЈ Р˜СЃРї ании 100 Р · олотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотР°uq ~ t &РЈ Р˜СЃРїР°РЅРёРё 6 R їСЂРѕРІРёРЅС† РёР№t %РЈ Р РѕСЃСЃРёРё 10 провнинцийt &РЈ Фра нции 8 проввинцинА Und beim Versuch, es zu deserialisieren, ist Folgendes passiert: InvalidClassException: lokale Klasse inkompatibel: stream classdesc serialVersionUID = - 196410440475012755, lokale Klasse serialVersionUID = -6675950253085108747 Dies ist die gleiche Ausnahme, die oben erwähnt wurde. Mehr dazu können Sie im Artikel eines unserer Studenten lesen. Übrigens haben wir einen wichtigen Punkt übersehen. Es ist klar, dass Strings und Grundelemente lassen sich leicht serialisieren: Java hat sicherlich einige – dann gibt es dafür eingebaute Mechanismen. Was aber, wenn unsere serializable-Klasse Felder hat, die nicht als Grundelemente, sondern als Verweise auf andere Objekte ausgedrückt werden? Lassen Sie uns zum Beispiel separate Klassen erstellen, TerritoriesInfoum mit unserer Klasse zu arbeiten . ResourcesInfoDiplomacyInfoSavedGame
public class TerritoriesInfo {

   private String info;

   public TerritoriesInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "TerritoriesInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class ResourcesInfo {

   private String info;

   public ResourcesInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "ResourcesInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}

public class DiplomacyInfo {

   private String info;

   public DiplomacyInfo(String info) {
       this.info = info;
   }

   public String getInfo() {
       return info;
   }

   public void setInfo(String info) {
       this.info = info;
   }

   @Override
   public String toString() {
       return "DiplomacyInfo{" +
               "info='" + info + '\'' +
               '}';
   }
}
Aber jetzt haben wir eine Frage: Sollten alle diese Klassen serialisierbar sein, wenn wir die geänderte Klasse serialisieren möchten SavedGame?
import java.io.Serializable;
import java.util.Arrays;

public class SavedGame implements Serializable {

   private TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   public TerritoriesInfo getTerritoriesInfo() {
       return territoriesInfo;
   }

   public void setTerritoriesInfo(TerritoriesInfo territoriesInfo) {
       this.territoriesInfo = territoriesInfo;
   }

   public ResourcesInfo getResourcesInfo() {
       return resourcesInfo;
   }

   public void setResourcesInfo(ResourcesInfo resourcesInfo) {
       this.resourcesInfo = resourcesInfo;
   }

   public DiplomacyInfo getDiplomacyInfo() {
       return diplomacyInfo;
   }

   public void setDiplomacyInfo(DiplomacyInfo diplomacyInfo) {
       this.diplomacyInfo = diplomacyInfo;
   }

   @Override
   public String toString() {
       return "SavedGame{" +
               "territoriesInfo=" + territoriesInfo +
               ", resourcesInfo=" + resourcesInfo +
               ", diplomacyInfo=" + diplomacyInfo +
               '}';
   }
}
Schauen wir uns das mal in der Praxis an! Lassen wir zunächst alles so, wie es ist, und versuchen wir, das Objekt zu serialisieren SavedGame:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // unser Objekt erstellen
       TerritoriesInfo territoriesInfo = new TerritoriesInfo(„Spanien hat 6 Provinzen, Russland hat 10 Provinzen, Frankreich hat 8 Provinzen“);
       ResourcesInfo resourcesInfo = new ResourcesInfo(„Spanien hat 100 Gold, Russland hat 80 Gold, Frankreich hat 90 Gold“);
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo(„Frankreich befindet sich im Krieg mit Russland, Spanien hat eine Neutralitätsposition eingenommen“);


       SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}
Ergebnis: Ausnahme im Thread „main“ java.io.NotSerializableException: DiplomacyInfo fehlgeschlagen! Eigentlich ist hier die Antwort auf unsere Frage. Wenn Sie ein Objekt serialisieren, werden alle Objekte serialisiert, auf die es in seinen Instanzvariablen verweist. Und wenn diese Objekte auch auf dritte Objekte verweisen, werden sie ebenfalls serialisiert. Und so weiter bis ins Unendliche. Alle Klassen in dieser Kette müssen serialisierbar sein, andernfalls sind sie nicht serialisierbar und es wird eine Ausnahme ausgelöst. Dies kann übrigens in Zukunft zu Problemen führen. Was sollen wir beispielsweise tun, wenn wir bei der Serialisierung einen Teil der Klasse nicht benötigen? Oder wir haben beispielsweise TerritoryInfoeine Klasse in unserem Programm als Teil einer Bibliothek geerbt. Allerdings ist es nicht serialisierbar und wir können es dementsprechend nicht ändern. Es stellt sich heraus, dass wir TerritoryInfounserer Klasse kein Feld hinzufügen können, da dann die gesamte Klasse nicht mehr serialisierbar wäre! Problem:/ Probleme dieser Art werden in Java mit dem Schlüsselwort gelöst . Wenn Sie dieses Schlüsselwort zu einem Klassenfeld hinzufügen, wird der Wert dieses Felds nicht serialisiert. Versuchen wir, eines der Felder unserer Klasse zu erstellen . Anschließend werden wir ein Objekt serialisieren und wiederherstellen. SavedGameSavedGameSerialisierung und Deserialisierung in Java - 2transientSavedGame transient
import java.io.Serializable;

public class SavedGame implements Serializable {

   private transient TerritoriesInfo territoriesInfo;
   private ResourcesInfo resourcesInfo;
   private DiplomacyInfo diplomacyInfo;

   public SavedGame(TerritoriesInfo territoriesInfo, ResourcesInfo resourcesInfo, DiplomacyInfo diplomacyInfo) {
       this.territoriesInfo = territoriesInfo;
       this.resourcesInfo = resourcesInfo;
       this.diplomacyInfo = diplomacyInfo;
   }

   //...Getter, Setter, toString()...
}



import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

   public static void main(String[] args) throws IOException {

       // unser Objekt erstellen
       TerritoriesInfo territoriesInfo = new TerritoriesInfo(„Spanien hat 6 Provinzen, Russland hat 10 Provinzen, Frankreich hat 8 Provinzen“);
       ResourcesInfo resourcesInfo = new ResourcesInfo(„Spanien hat 100 Gold, Russland hat 80 Gold, Frankreich hat 90 Gold“);
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo(„Frankreich befindet sich im Krieg mit Russland, Spanien hat eine Neutralitätsposition eingenommen“);


       SavedGame savedGame = new SavedGame(territoriesInfo, resourcesInfo, diplomacyInfo);

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       objectOutputStream.writeObject(savedGame);

       objectOutputStream.close();
   }
}


import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

       SavedGame savedGame = (SavedGame) objectInputStream.readObject();

       System.out.println(savedGame);

       objectInputStream.close();


   }
}
Und hier ist das Ergebnis: SavedGame{territoriesInfo=null, resourcesInfo=ResourcesInfo{info='Spanien hat 100 Gold, Russland hat 80 Gold, Frankreich hat 90 Gold'}, diplomacyInfo=DiplomacyInfo{info='Frankreich befindet sich im Krieg mit Russland, Spanien hat eine neutrale Position eingenommen.}} Gleichzeitig erhielten wir eine Antwort auf die Frage, welcher Wert transientdem -Feld zugewiesen wird. Ihm wird ein Standardwert zugewiesen. Im Falle von Objekten ist dies null. In aller Ruhe können Sie diesen hervorragenden Artikel über Serialisierung lesen . Es geht auch um die Schnittstelle Externalizable, über die wir in der nächsten Vorlesung sprechen werden. Darüber hinaus gibt es im Buch „Head-First Java“ ein Kapitel zu diesem Thema, achten Sie darauf :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION