1. 程式人生 > >基礎 | Java序列化與反序列化的底層實現

基礎 | Java序列化與反序列化的底層實現

深拷貝與淺拷貝中,提到可以採用「序列化與反序列化」的方式來實現深拷貝,今天主要來填一下序列化的坑。

其中,序列化是一種物件持久化的手段,普遍應用於網路傳輸和遠端方法呼叫(RMI)等場景中,建議關注。


什麼是Java序列化和反序列化?

參考答案:

在Java中,序列化是指將Java物件轉換為位元組序列的過程,而反序列化是指將位元組序列轉換為Java物件的過程。其中,位元組序列即是二進位制資料,可以方便地在網路上傳輸或儲存到本地硬碟中。


為什麼要進行序列化和反序列化?

問題分析:可以從序列化和反序列化的主要作用或應用場景進行總結。

參考答案:

序列化的主要作用包括兩個方面:

  • 實現網路中物件的傳送,即在網路程序間傳遞物件。
  • 實現記憶體中物件的持久化,即將物件儲存到本地磁碟中。

主要應用場景:

  • Java遠端方法呼叫(RMI),即允許一個Java虛擬機器上執行的Java程式呼叫其他虛擬機器執行記憶體中物件的方法,即使這些虛擬機器運行於物理隔離的不同主機上。
  • 分散式系統中不同伺服器間共享的JavaBean物件都需要先序列化為二進位制資料,再在網路中傳輸。
  • Session鈍化機制:Web容器將一些session先序列化寫入本地磁碟,在需要使用時再將物件從磁碟還原到記憶體中去,以減輕伺服器的記憶體負擔。

備註:當然還有很多應用場景,在此僅做參考。


如何實現Java序列化?

問題分析:同「請解釋一下Serializable介面的作用」。

參考答案:

第一步:將需要序列化的物件所屬類實現java.io.Serializable標記介面(沒有任何抽象方法的介面)。

public class Person implements Serializable {
	private static final long serialVersionUID = -7755892886656448346L;
	private String name;
	private Integer age;
// getter、setter、constructor和toString方法省略 }

第二步:在Java程式中使用物件輸出流(ObjectOutputStream),通過其writeObject()方法來實現序列化操作。

OutputStream out = new FileOutputStream("D:\\Jerry.txt");
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(new Person("Jerry", 18));
oos.flush();

第三步:在Java程式中使用物件輸入出流(ObjectInputStream),通過其readObject()方法來實現反序列化操作。

InputStream is = new FileInputStream("D:\\Jerry.txt");
ObjectInputStream ois = new ObjectInputStream(is);
Person jerry = (Person) ois.readObject();

常見問題:

  • 若物件所屬類未實現Serializable介面,則在序列化時會報java.io.NotSerializableException執行時異常。
  • 建議物件所屬類在實現Serializable介面的同時,新增serialVersionUID常量。

注意事項

  • 序列化時,僅對物件的狀態(屬性值)進行儲存,而不管物件的方法。
  • 靜態成員資料不能被序列化,因為static代表類的狀態。
  • 在變數宣告前新增transient關鍵字,可在序列化時忽略該資料。

擴充套件面試題

問:如何實現自定義序列化策略?

方式一:實現Externalizable介面,並重寫WriteExternal(ObjectOutput)和readExternal(ObjectInput)方法。其中,Externalizable介面繼承了Serializable介面,並添加了以上兩個方法,用於實現對序列化的控制。

方式二:根據Externalizable介面的思想,可在實現Serializable介面的基礎上,直接新增並實現WriteObject(ObjectOutputStream)和readObject(ObjectInputStream)兩個方法。

其中,ArrayList類即採用方式二來實現對序列化的控制,主要是為了保證僅對動態陣列中的非null元素進行序列化。

問:serialVersionUID常量有什麼作用?

答:保證版本號的一致性。若不顯式指定該常量值,Java編譯器會自動為其生成一個預設值,該預設值會隨著類程式碼的變動而變動。故當修改類程式碼時,之前已經序列化的物件在進行反序列化時即會因為版本號的不一致而發生異常。

顯式定義serialVersionUID主要有兩種用途:

  • 在某些場合,希望類的不同版本對序列化相容,故需確保類的不同版本具有相同的serialVersionUID;
  • 在某些場合,不希望類的不同版本對序列化相容,因此需要確保類的不同版本具有不同的serialVersionUID。

問:序列化為什麼可以實現深拷貝?

答:若一個物件的屬性引用其他物件,則序列化該物件時引用物件也會同時被序列化,這即是序列化能夠實現深拷貝的本質。


推薦閱讀


歡迎關注

Java名企面試吧,每天10點24分,我們不見不散!

丙子先生的宗旨是,每天以短篇幅講高頻面試題,不增加太多負擔,但需要持之以恆。

能力有限,歡迎指教!