CodeGym /Java Blog /Toto sisi /Java中的序列化和反序列化有什麼區別?
John Squirrels
等級 41
San Francisco

Java中的序列化和反序列化有什麼區別?

在 Toto sisi 群組發布
你好!在今天的課程中,我們將討論 Java 中的序列化和反序列化。我們將從一個簡單的例子開始。假設您創建了一款電腦遊戲。如果你在 90 年代長大並記得那個時代的遊戲機,你可能知道他們缺少我們今天認為理所當然的東西——保存和加載遊戲的能力 :) 如果沒有,想像一下! Java中的序列化和反序列化有什麼區別? - 1 恐怕沒有這些能力的遊戲今天就完蛋了!“保存”和“加載”遊戲到底是什麼意思?好吧,我們理解普通的意思:我們想從我們離開的地方繼續遊戲。為此,我們創建了一種“檢查點”,然後我們用它來加載遊戲。但這對程序員而不是休閒遊戲玩家意味著什麼?答案很簡單:我們'. 假設您在 Strategium 中扮演西班牙。你的遊戲有一個狀態:誰擁有哪些領土,誰擁有多少資源,誰與誰結盟,誰與誰交戰,等等。我們必須以某種方式保存這些信息,即我們程序的狀態,以便將來恢復它並繼續遊戲。因為這正是序列化去序列化的目的。 序列化是將對象的狀態存儲在字節序列中的過程。 反序列化是從這些字節中恢復對象的過程。任何 Java 對像都可以轉換為字節序列。我們為什麼需要那個?我們不止一次說過,程序本身並不存在。大多數情況下,它們與其他程序交互、交換數據等。字節序列是一種方便高效的格式。例如,我們可以將我們的SavedGame對像變成字節序列,通過網絡將這些字節發送到另一台計算機,然後在第二台計算機上將這些字節轉回 Java 對象!聽起來很難,對吧?實施這個過程似乎很痛苦:/幸運的是,事實並非如此!:) 在 Java 中,Serializable接口負責序列化過程。這個接口非常簡單:你不需要實現一個方法就可以使用它!這就是我們的遊戲保存類看起來多麼簡單:

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) +
               '}';
   }
}
三陣分別負責領土、資源、外交等信息。Serializable 接口告訴 Java 虛擬機:“一切正常——如有必要,可以序列化此類的對象”。沒有單一接口的接口看起來很奇怪:/為什麼有必要?這個問題的答案見上文:它只是起到提供給Java虛擬機必要的信息的作用。在我們之前的一節課中,我們簡要提到了標記接口。這些是特殊的信息接口,它們簡單地用將來對 Java 機器有用的附加信息標記我們的類。他們沒有任何您必須實施的方法。Serializable是這些接口之一。另一個要點:為什麼我們需要private static final long serialVersionUID在類中定義的變量?為什麼需要它?該字段包含序列化類版本的唯一標識符。任何實現該Serializable接口的類都有一個version標識符。它是根據類的內容計算的:它的字段、它們聲明的順序、方法等。如果我們更改字段類型和/或類中字段的數量,那麼版本標識符會立即更改. serialVersionUID類序列化時也會寫入。當我們嘗試反序列化時,即從一組字節中恢復一個對象時,serialVersionUID將關聯的值與serialVersionUID對於我們計劃中的課程。如果值不匹配,則 java.io. 將拋出InvalidClassException 。我們將在下面看到一個例子。為避免這種情況,我們只需在我們的類中手動設置版本標識符。在我們的例子中,它將簡單地等於 1(但您可以替換為您喜歡的任何其他數字)。好了,是時候嘗試序列化我們的SavedGame對象,看看會發生什麼了!

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();
   }
}
如您所見,我們創建了 2 個流:FileOutputStreamObjectOutputStream. 第一個可以將數據寫入文件,第二個可以將對象轉換為字節。您已經new BufferedReader(new InputStreamReader(...))在前面的課程中看到過類似的“嵌套”構造,例如 ,所以這些不會嚇到您 :) 通過創建這樣一個包含兩個流的“鏈”,我們執行了兩個任務:我們將對象轉換SavedGame為一個集合字節並使用該writeObject()方法將其保存到文件中。而且,順便說一句,我們甚至沒有看我們得到了什麼!是時候查看文件了! *注意:您不必提前創建文件。如果不存在具有該名稱的文件,它將自動創建* 這是它的內容!

¬н 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 провинций
呃哦 :( 看來我們的程序沒有運行 :( 事實上,它確實運行了。你還記得我們向文件發送了一組字節,而不僅僅是一個對像或文本嗎?好吧,這就是那個一組字節看起來像 :) 這是我們保存的遊戲!如果我們想恢復我們的原始對象,即從我們停止的地方開始並繼續遊戲,那麼我們需要反向過程:反序列化。這是我們的遊戲中樣子案件:

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);
   }
}
這就是結果!

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]}
出色的!我們設法先將游戲狀態保存到文件中,然後從文件中恢復它。現在讓我們嘗試做同樣的事情,但是沒有我們SavedGame類的版本標識符。我們不會重寫我們的兩個類。他們的代碼將保持不變,但我們將從類private static final long serialVersionUID中刪除SavedGame這是序列化後的對象:

¬н 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 провинций
但是看看當我們嘗試反序列化它時會發生什麼:

InvalidClassException: local class incompatible: stream classdesc serialVersionUID = -196410440475012755, local class serialVersionUID = -6675950253085108747
這是我們上面提到的例外。順便說一句,我們錯過了一些重要的事情。字符串和原語可以很容易地序列化是有道理的:Java 可能有某種內置機制可以做到這一點。但是,如果我們的serializable類的字段不是原始字段,而是對其他對象的引用怎麼辦?例如,讓我們創建單獨的TerritoriesInfo,ResourcesInfoDiplomacyInfo類來處理我們的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 + '\'' +
               '}';
   }
}
現在出現了一個問題:如果我們想要序列化我們改變的類,是否需要所有這些類?SerializableSavedGame

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 +
               '}';
   }
}
好吧,讓我們測試一下!讓我們保持原樣並嘗試序列化一個SavedGame對象:

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();
   }
}
結果:

Exception in thread "main" java.io.NotSerializableException: DiplomacyInfo
沒用!基本上,這就是我們問題的答案。當一個對像被序列化時,它的實例變量引用的所有對像都被序列化。 如果這些對像還引用了其他對象,那麼它們也會被序列化。依此類推。這個鏈中的所有類都必須是Serializable,否則無法序列化,會拋出異常。順便說一句,這可能會在未來產生問題。例如,如果我們在序列化時不需要某個類的一部分,我們應該怎麼做?或者,例如,如果該類TerritoryInfo作為某個第三方庫的一部分提供給我們怎麼辦。並進一步假設它不是Serializable,因此我們無法更改它。事實證明,我們無法TerritoryInfo在我們的SavedGame類,因為這樣做會使整個SavedGame類不可序列化!That's a problem:/ Java中的序列化和反序列化有什麼區別? - 2在 Java 中,這類問題使用transient關鍵字來解決。如果將此關鍵字添加到類的某個字段,則該字段不會被序列化。讓我們嘗試使類的實例字段之一成為SavedGame瞬態的。然後我們將序列化並恢復一個對象。

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();


   }
}
結果如下:

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'}}
此外,我們得到了有關為字段分配什麼值的問題的答案transient。它被分配了默認值。對於對象,這是null. 如果您有幾分鐘空閒時間,可以閱讀這篇關於連載的優秀文章。它還提到了Externalizable接口,我們將在下一課中討論。此外,“Head-First Java”一書有一章是關於這個主題的。給它一些關注:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION