- 把Java對象轉換為位元組序列的過程稱為對象的序列化。
- 把位元組序列恢複為Java對象的過程稱為對象的反序列化。
- java中引入序列化機制主要是為了支援兩種重要技術:RMI和JavaBean技術。
-
對象的序列化主要有兩種用途:
1) 把對象的位元組序列永久地儲存到硬碟上,通常存放在一個檔案中;
2) 在網絡上傳送對象的位元組序列。
-
隻有實作了Serializable和Externalizable接口的類的對象才能被序列化。
1)Serializable接口可使類中的所有成員變量自動被序列化(transient和static修飾的變量除外),預設的
序列化方式會序列化整個對象圖,這需要遞歸周遊對象圖。如果對象圖很複雜,遞歸周遊操作需要消耗很多
的空間和時間,它的内部資料結構為雙向清單。在應用時,如果對某些成員變量都改為transient類型,将
節省空間和時間,提高序列化的性能。
2)Externalizable接口繼承自Serializable接口,實作Externalizable接口的類完全由自身來控制序列化
的行為,即Externalizable對象預設情況下不儲存任何它的字段,而僅實作Serializable接口的類可以
采用預設的序列化方式 。
那我們如何對一個Serializable對象的序列化和反序列化行為進行控制呢?
1)加transient修飾符,這樣改變量就不會被序列化了
2)添加(不是“實作”和“重載” )writeObject和readObject方法: private void writeObject(ObjectOutputStream stream) throws IOException; private void readObject(ObjectOutputStream stream) throws IOException;
這樣一旦對象被序列化或者被反序列化,就會自動分别調用這兩個方法,而不會調用預設的序列化和反序列化方法。(這一點确實感覺有點奇怪,或者叫混亂!)
當ObjectOutputStream對一個Serializable對象進行序列化時,如果該對象具有writeObject()方法,那麼就會執行這一方法,否則就按預設方式序列化。在該對象的writeObjectt()方法中,可以先調用ObjectOutputStream的defaultWriteObject()方法,使得對象輸出流先執行預設的序列化操作。同理可得出反序列化的情況,不過這次是defaultReadObject()方法。
- 對Serializable對象反序列化時,并不會調用任何構造函數 ,是以Serializable類無需預設構造函數,但是當Serializable類的父類沒有實作Serializable接口時,反序列化過程會調用父類的預設構造函數,是以該父類必需有預設構造函數,否則會抛異常。
- 對Externalizable對象反序列化時,會先調用類的不帶參數的構造方法 ,這是有别于預設反序列方式的。如果把類的不帶參數的構造方法删除,或者把該構造方法的通路權限設定為private、預設或protected級别,會抛出java.io.InvalidException: no valid constructor異常,是以Externalizable對象必須有預設構造函數,而且必需是public的。
-
對象序列化包括如下步驟:
1) 建立一個對象輸出流,它可以包裝一個其他類型的目标輸出流,如檔案輸出流;
2) 通過對象輸出流的writeObject()方法寫對象。
-
對象反序列化的步驟如下:
1) 建立一個對象輸入流,它可以包裝一個其他類型的源輸入流,如檔案輸入流;
2) 通過對象輸入流的readObject()方法讀取對象。
下面是個簡單的例子: import java.io.*; import java.util.Date; public class Test{ public static void main(String[] args) throws Exception { //序列化對象 ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("c:/objectFile.data")); MyObject obj1= new MyObject("liuhongliang", 24); out.writeObject("hello world!"); out.writeObject(new Date()); out.writeObject(obj1); out.writeInt(54); out.close(); //反序列化對象 ObjectInputStream in = new ObjectInputStream( new FileInputStream(("c:/objectFile.data")); System.out.println("obj1=" + (String) in.readObject()); System.out.println("obj2=" + (Date) in.readObject()); MyObject obj3 = (MyObject) in.readObject(); System.out.println("obj3=" + obj3); int obj4 = in.readInt(); System.out.println("obj4=" + obj4); in.close(); } } //自定義可序列化類 class MyObject implements Serializable { private String name; private int age; public MyObject(String name, int age) { this.name = name; this.age = age; } public String toString() { return "name=" + name + ", age=" + age; } }
-
關于serialVersionUID
在Loong(我們公司自己開發的基于OSGI的網絡應用伺服器平台)中內建Glassfish 的資料源時遇到了序列化的問題:在Glassfish中序列化的連接配接池對象,在Loong裡面反序列化時總是不成功!後來查了相關資料,原來是連接配接池類出了問題:隻實作了Serializable接口,沒有指定具體的serialVersionUID。
凡是實作Serializable接口的類都應當有一個表示序列化版本辨別符的靜态變量:
private static final long serialVersionUID;
以上serialVersionUID的取值是Java運作時環境根據類的内部細節自動生成的。如果對類的源代碼作了修改,再重新編譯,新生成的類檔案的serialVersionUID的取值有可能也會發生變化。
類的serialVersionUID的預設值完全依賴于Java編譯器的實作,對于同一個類,用不同的Java編譯器編譯,有可能會導緻不同的serialVersionUID,也有可能相同。為了提高哦啊serialVersionUID的獨立性和确定性,強烈建議 在一個可序列化類中顯示的定義serialVersionUID,為它賦予明确的值。
顯式地定義serialVersionUID有兩種用途:
1) 在某些場合,希望類的不同版本對序列化相容,是以需要確定類的不同版本具有相同的serialVersionUID;
2) 在某些場合,不希望類的不同版本對序列化相容,是以需要確定類的不同版本具有不同的serialVersionUID。
-
利用java的序列化和反序列化機制可以實作對任何可Serializable的對象的深度複制
上面的例子裡,現将MyObject對象序列化到硬碟檔案 c:/objectFile.data,然後反序列化之,這樣可以實作深度複制,但是由于操作硬碟,是以當該操作比較頻繁時會嚴重影響系統性能,上面是用檔案流方式的實作,我們可以用位元組流方式來高效的實作深度複制: import java.io.*; import java.util.Date; public class Test2 { public static void main(String[] args) throws Exception { //序列化對象 ObjectOutputStream out = new ObjectOutputStream( new ByteArrayOutputStream()); MyObject obj1= new MyObject("liuhongliang", 24); out.writeObject("hello world!"); out.writeObject(new Date()); out.writeObject(obj1); out.writeInt(54); out.close(); //反序列化對象 ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream(out.toByteArray())); System.out.println("obj1=" + (String) in.readObject()); System.out.println("obj2=" + (Date) in.readObject()); MyObject obj3 = (MyObject) in.readObject(); System.out.println("obj3=" + obj3); int obj4 = in.readInt(); System.out.println("obj4=" + obj4); in.close(); } } //自定義可序列化類 class MyObject implements Serializable { private String name; private int age; public MyObject(String name, int age) { this.name = name; this.age = age; } public String toString() { return "name=" + name + ", age=" + age; } }