CodeGym/Java-Blog/Random-DE/Was ist der Unterschied zwischen Serialisierung und Deser...
Autor
Aditi Nawghare
Software Engineer at Siemens

Was ist der Unterschied zwischen Serialisierung und Deserialisierung in Java?

Veröffentlicht in der Gruppe Random-DE
Hallo! In der heutigen Lektion sprechen wir über Serialisierung und Deserialisierung in Java. Wir beginnen mit einem einfachen Beispiel. Nehmen wir an, Sie haben ein Computerspiel erstellt. Wenn Sie in den 90er Jahren aufgewachsen sind und sich an die Spielekonsolen dieser Zeit erinnern, wissen Sie wahrscheinlich, dass ihnen etwas fehlte, das wir heute für selbstverständlich halten – die Möglichkeit, Spiele zu speichern und zu laden :) Wenn nicht, stellen Sie sich das vor! Was ist der Unterschied zwischen Serialisierung und Deserialisierung in Java?  - 1 Ich fürchte, dass heute ein Spiel ohne diese Fähigkeiten zum Scheitern verurteilt wäre! Was bedeutet es überhaupt, ein Spiel zu „speichern“ und zu „laden“? Nun, wir verstehen die gewöhnliche Bedeutung: Wir wollen das Spiel dort fortsetzen, wo wir aufgehört haben. Dazu erstellen wir eine Art „Checkpoint“, über den wir dann das Spiel laden. Aber was bedeutet das für einen Programmierer und nicht für einen Gelegenheitsspieler? Die Antwort ist einfach: Wir. Nehmen wir an, Sie spielen als Spanien in Strategium. Ihr Spiel hat einen Zustand: Wem gehören welche Gebiete, wer hat wie viele Ressourcen, wer ist mit wem in einer Allianz, wer führt mit wem Krieg und so weiter. Wir müssen diese Informationen, den Status unseres Programms, irgendwie speichern, um sie in Zukunft wiederherzustellen und das Spiel fortzusetzen. Denn genau dafür sind Serialisierung und Deserealisierung da. Bei der Serialisierung wird der Zustand eines Objekts in einer Bytefolge gespeichert. Deserialisierungist der Prozess der Wiederherstellung eines Objekts aus diesen Bytes. Jedes Java-Objekt kann in eine Bytesequenz konvertiert werden. Warum sollten wir das brauchen? Wir haben mehr als einmal gesagt, dass Programme nicht für sich allein existieren. Meistens interagieren sie mit anderen Programmen, tauschen Daten aus usw. Und eine Bytefolge ist ein praktisches und effizientes Format. Beispielsweise können wir unser SavedGameObjekt in eine Folge von Bytes umwandeln, diese Bytes über das Netzwerk an einen anderen Computer senden und diese Bytes dann auf dem zweiten Computer wieder in ein Java-Objekt umwandeln! Klingt schwierig, oder? Und die Implementierung dieses Prozesses scheint mühsam zu sein :/ Glücklicherweise ist das nicht so! :) In Java ist dasSerializableDie Schnittstelle ist für den Serialisierungsprozess verantwortlich. Diese Schnittstelle ist äußerst einfach: Sie müssen keine einzige Methode implementieren, um sie zu verwenden! So einfach sieht unser Sparkurs aus:
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) +
               '}';
   }
}
Die drei Arrays sind für Informationen über Gebiete, Ressourcen und Diplomatie verantwortlich. Die Serializable-Schnittstelle teilt der Java Virtual Machine mit: „ Alles ist in Ordnung – bei Bedarf können Objekte dieser Klasse serialisiert werden .“ Eine Schnittstelle ohne eine einzige Schnittstelle sieht seltsam aus :/ Warum ist das notwendig? Die Antwort auf diese Frage ist oben zu sehen: Sie dient lediglich dazu, der Java Virtual Machine die notwendigen Informationen bereitzustellen. In einer unserer vorherigen Lektionen haben wir Marker-Schnittstellen kurz erwähnt . Hierbei handelt es sich um spezielle Informationsschnittstellen, die unsere Klassen einfach mit zusätzlichen Informationen markieren, die für die Java-Maschine in Zukunft nützlich sein werden. Sie verfügen über keine Methoden, die Sie implementieren müssen.Serializableist eine dieser Schnittstellen. Ein weiterer wichtiger Punkt: Warum brauchen wir die private static final long serialVersionUIDVariable, die wir in der Klasse definiert haben? Warum wird es benötigt? Dieses Feld enthält eine eindeutige Kennung für die Version der serialisierten Klasse . Jede Klasse, die die SerializableSchnittstelle implementiert, verfügt über einen versionBezeichner. Sie wird basierend auf dem Inhalt der Klasse berechnet: ihren Feldern, der Reihenfolge, in der sie deklariert werden, Methoden usw. Wenn wir den Feldtyp und/oder die Anzahl der Felder in unserer Klasse ändern, ändert sich sofort die Versionskennung . serialVersionUIDwird auch geschrieben, wenn die Klasse serialisiert wird. Wenn wir versuchen, ein Objekt zu deserialisieren, also aus einer Reihe von Bytes wiederherzustellen, serialVersionUIDwird der zugehörige Wert mit dem Wert von verglichenserialVersionUIDfür die Klasse in unserem Programm. Wenn die Werte nicht übereinstimmen, wird ein java.io. Es wird eine InvalidClassException ausgelöst. Ein Beispiel dafür sehen wir weiter unten. Um dies zu vermeiden, setzen wir die Versionskennung einfach manuell in unserer Klasse. In unserem Fall ist es einfach gleich 1 (Sie können es aber durch eine beliebige andere Zahl ersetzen). SavedGameNun ist es an der Zeit, unser Objekt zu serialisieren und 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 {

       // Create our object
       String[] territoryInfo = {"Spain has 6 provinces", "Russia has 10 provinces", "France has 8 provinces"};
       String[] resourcesInfo = {"Spain has 100 gold", "Russia has 80 gold", "France has 90 gold"};
       String[] diplomacyInfo = {"France is at war with Russia, Spain has taken a neutral position"};

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

       // Create 2 streams to serialize the object and save it to a file
       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

       // Save the game to a file
       objectOutputStream.writeObject(savedGame);

       // Close the stream and free resources
       objectOutputStream.close();
   }
}
Wie Sie sehen können, haben wir zwei Streams erstellt: FileOutputStreamund ObjectOutputStream. Der erste kann Daten in eine Datei schreiben und der zweite konvertiert Objekte in Bytes. Ähnliche „verschachtelte“ Konstrukte, zum Beispiel , haben Sie bereits new BufferedReader(new InputStreamReader(...))in früheren Lektionen gesehen, diese sollten Sie also nicht abschrecken :) Indem wir eine solche „Kette“ aus zwei Streams erstellen, führen wir beide Aufgaben aus: Wir wandeln das SavedGameObjekt in eine Menge um von Bytes und speichern Sie es mit der Methode in einer Datei writeObject(). Und übrigens haben wir nicht einmal darauf geachtet, was wir bekommen haben! Es ist Zeit, sich die Datei anzusehen! *Hinweis: Sie müssen die Datei nicht im Voraus erstellen. Wenn eine Datei mit diesem Namen nicht existiert, wird sie automatisch erstellt* Und hier ist ihr Inhalt!
¬н sr SavedGame [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Oh-oh :( Es scheint, dass unser Programm nicht funktioniert hat :( Tatsächlich hat es funktioniert. Erinnern Sie sich, dass wir eine Reihe von Bytes, nicht nur ein Objekt oder Text, an die Datei gesendet haben? Nun, das ist es Satz von Bytes sieht so aus :) Das ist unser gespeichertes Spiel! Wenn wir unser ursprüngliches Objekt wiederherstellen wollen, also das Spiel dort starten und fortsetzen wollen, wo wir aufgehört haben, dann brauchen wir den umgekehrten Prozess: Deserialisierung. So wird es in unserem aussehen Fall:
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=["Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces], resourcesInfo=[Spain has 100 gold, Russia has 80 gold, France has 90 gold], diplomacyInfo=[France is at war with Russia, Spain has taken a neutral position]}
Exzellent! 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, jedoch ohne die Versionskennung für unsere SavedGameKlasse. Wir werden nicht beide Klassen neu schreiben. Ihr Code bleibt derselbe, wir werden ihn jedoch private static final long serialVersionUIDaus der SavedGameKlasse entfernen. Hier ist unser Objekt nach der Serialisierung:
¬н sr SavedGameі€MіuОm‰ [ diplomacyInfot [Ljava/lang/String;[ resourcesInfoq ~ [ territoriesInfoq ~ xpur [Ljava.lang.String;­ТVзй{G xp t pФранция воюет СЃ Россией, Испания заняла позицию нейтралитетаuq ~ t "РЈ Испании 100 золотаt РЈ Р РѕСЃСЃРёРё 80 золотаt !РЈ Франции 90 золотаuq ~ t &РЈ Испании 6 провинцийt %РЈ Р РѕСЃСЃРёРё 10 провинцийt &РЈ Франции 8 провинций
Aber schauen Sie sich an, was passiert, wenn wir versuchen, es zu deserialisieren:
InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
Dies ist genau die Ausnahme, die wir oben erwähnt haben. Übrigens haben wir etwas Wichtiges verpasst. Es macht Sinn, dass Strings und Grundelemente leicht serialisiert werden können: Java verfügt wahrscheinlich über einen eingebauten Mechanismus, um dies zu tun. Was aber, wenn unsere serializableKlasse Felder hat, die keine Grundelemente, sondern Verweise auf andere Objekte sind? Lassen Sie uns zum Beispiel separate TerritoriesInfo, ResourcesInfound DiplomacyInfo-Klassen erstellen, um mit unserer Klasse zu arbeiten SavedGame.
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 + '\'' +
               '}';
   }
}
Und jetzt stellt sich die Frage: Müssen alle diese Klassen vorhanden sein, Serializablewenn wir unsere 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 +
               '}';
   }
}
Dann testen wir es! Lassen wir alles so wie es ist und versuchen wir ein SavedGameObjekt zu serialisieren:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Main {

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

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       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:
Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
Es hat nicht funktioniert! Im Grunde ist das die Antwort auf unsere Frage. Wenn ein Objekt serialisiert wird, werden alle von seinen Instanzvariablen referenzierten Objekte serialisiert. Und wenn diese Objekte auch auf andere Objekte verweisen, werden sie ebenfalls serialisiert. Und so weiter bis ins Unendliche. Alle Klassen in dieser Kette müssen seinSerializable , andernfalls können sie nicht serialisiert werden und es wird eine Ausnahme ausgelöst. Dies kann übrigens später zu Problemen führen. Was sollen wir tun, wenn wir beispielsweise bei der Serialisierung keinen Teil einer Klasse benötigen? Oder was wäre zum Beispiel, wenn die TerritoryInfoKlasse als Teil einer Bibliothek eines Drittanbieters zu uns käme? Und nehmen wir weiter an, dass dies nicht der Fall ist Serializableund wir es dementsprechend nicht ändern können. TerritoryInfoEs stellt sich heraus, dass wir unserem kein Feld hinzufügen könnenSavedGameKlasse, da sonst die gesamte SavedGameKlasse nicht serialisierbar wäre! Das ist ein Problem :/ Was ist der Unterschied zwischen Serialisierung und Deserialisierung in Java?  - 2In Java werden Probleme dieser Art mit dem transientSchlüsselwort gelöst. Wenn Sie dieses Schlüsselwort zu einem Feld Ihrer Klasse hinzufügen, wird dieses Feld nicht serialisiert. Versuchen wir, eines der SavedGameInstanzfelder der Klasse transient zu machen. Dann werden wir ein Objekt serialisieren und wiederherstellen.
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;
   }

   // ...getters, setters, toString()
}



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

public class Main {

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

       // Create our object
       TerritoryInfo territoryInfo = new TerritoryInfo("Spain has 6 provinces, Russia has 10 provinces, France has 8 provinces");
       ResourceInfo resourceInfo = new ResourceInfo("Spain has 100 gold, Russia has 80 gold, France has 90 gold");
       DiplomacyInfo diplomacyInfo =  new DiplomacyInfo("France is at war with Russia, Spain has taken a neutral position");


       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='Spain has 100 gold, Russia has 80 gold, France has 90 gold'}, diplomacyInfo=DiplomacyInfo{info='France is at war with Russia, Spain has taken a neutral position'}}
Außerdem haben wir eine Antwort auf unsere Frage erhalten, welcher Wert einem transientFeld zugewiesen wird. Ihm wird der Standardwert zugewiesen. Für Objekte ist dies null. Sie können diesen hervorragenden Artikel zur Serialisierung lesen , wenn Sie ein paar Minuten Zeit haben. Es erwähnt auch die ExternalizableSchnittstelle, über die wir in der nächsten Lektion sprechen werden. Darüber hinaus gibt es im Buch „Head-First Java“ ein Kapitel zu diesem Thema. Gib ihm etwas Aufmerksamkeit :)
Kommentare
  • Beliebt
  • Neu
  • Alt
Du musst angemeldet sein, um einen Kommentar schreiben zu können
Auf dieser Seite gibt es noch keine Kommentare