標籤:java序列化 serializab externaliz 還原序列化 對象序列化
Java中的序列化機制有兩種實現方式:
一種是實現Serializable介面
另一種是實現Externalizable介面
區別:
實現Serializable介面
1 系統自動儲存必要的資訊
2 Java內建支援,易於實現,只需實現該介面即可,無須任何代碼支援
3 效能略差
實現Externalizable介面
1 程式員決定儲存哪些資訊
2 僅僅提供兩個空方法,實現該介面必須為兩個空方法提供實現
3 效能略好
由於實現Externalizable介面導致了編程複雜度的增加,所以大部分時候採用實現Serializable介面方式實現序列化。
下面給出一個執行個體
public class PersonBean implements Serializable { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "name="+this.name + ",age=" + this.age; }}
這是一個PersonBean類,就這麼簡單已經實現了PersonBean類的序列化。
下面就可以使用ObjectInputStream、ObjectOutputStream類進行對象的寫入和讀取操作了。
代碼如下:
public static void main(String[] args) { PersonBean personBean1 = new PersonBean(); personBean1.setName("long"); personBean1.setAge(20); PersonBean personBean2 = new PersonBean(); personBean2.setName("fei"); personBean2.setAge(25); ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; String path = "D:\\Program Files (x86)\\ADT\\workspace\\JavaIO\\demoTest.txt"; try { FileOutputStream fileOutputStream = new FileOutputStream(path); FileInputStream fileInputStream = new FileInputStream(path); objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(personBean1); objectOutputStream.writeObject(personBean2); objectInputStream = new ObjectInputStream(fileInputStream); PersonBean personBean3 = (PersonBean)objectInputStream.readObject(); PersonBean personBean4 = (PersonBean) objectInputStream.readObject(); System.out.println(personBean3.toString()); System.out.println(personBean4.toString()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }finally{ if (objectOutputStream != null) { try { objectOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
結果圖:
Java中允許自訂序列化,提供了一個修飾符transient,該修飾符只能修飾Field欄位,不能修飾方法和類,該修飾符達到的效果是把被修飾的Field完全隔離在序列化機制之外。這樣導致在還原序列化回複java對象時無法取得該Field值。
現在把PeronBean類的age欄位使用該修飾符修飾,其他代碼保持不變如下:
public class PersonBean implements Serializable { private String name; private transient int age; ..............
然後再運行程式Demo,結果如下:
看到了結果大家應該明白了該修飾符的意義了吧。
自訂序列化機制還沒有這麼簡單,還可以更強大。
在序列化和還原序列化過程中需要特殊處理的類提供如下特殊簽名的方法,這些特殊的方法用以實現自訂序列化
private void writeObject(java.io.ObjectOutputStream) throws IOExceptionprivate void readObject(java.io.ObjectInputStream) throws IOException,ClassNotFoundExceptionprivate void readObjectNoData() throws ObjectStreamException
writeObject方法負責寫入特定累的執行個體狀態,readObject方法可以回複它
當序列流不完整時,readObjectNoData方法可以用來正確地初始化還原序列化的對象:例如,接收方使用的還原序列化類的版本不用於發送方,或者接收方版本擴充的類不是放鬆方版本擴充的類,或者序列化流被篡改時,系統都會調用readObjectNoData方法類初始化還原序列化的對象。
下面重寫PersonBean類,自訂序列化,代碼的如下:
public class PersonBean implements Serializable { private String name; private transient int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } private void writeObject(ObjectOutputStream out) throws IOException{ out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{ this.name = ((StringBuffer)in.readObject()).toString(); this.age = in.readInt(); } @Override public String toString() { return "name="+this.name + ",age=" + this.age; }}
writeObject方法,把名字反轉後寫入,readObject方法直接讀取反轉後的名字即可。Demo類代碼不變,結果
從結果可以看出自訂序列化已經實現,並且writeObject、readObject也已經被調用了。
細心的讀者可能發現,PersonBean類中int age使用了transient修飾符修飾了,但是讀取之後的結果age不等於0??前面講過,使用transient修飾符修飾的field是完全隔離的。但是這裡需要注意的是,這裡的序列化是自訂的,自訂的過程中並沒有對該欄位進行處理,當然transient修飾符在這裡並不起作用了。。。
還有一種更加徹底的序列化自訂機制
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException
該方法前面的ANY-ACCESS-MODIFIER說明該方法可以用於private protected package-private等存取權限,其子類可能獲得該方法。
下面重寫PersonBean類,如下:
public class PersonBean implements Serializable { private String name; private transient int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } private void writeObject(ObjectOutputStream out) throws IOException{ out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{ this.name = ((StringBuffer)in.readObject()).toString(); this.age = in.readInt(); } @Override public String toString() { return "name="+this.name + ",age=" + this.age; } private Object writeReplace() throws ObjectStreamException{ ArrayList<Object> arrayList = new ArrayList<>(); arrayList.add(this.name); arrayList.add(this.age); return arrayList; }}
Java的序列化機制保證在序列化某個對象之前,先調用該對象的writeReplace方法,如果該方法返回一個Java對象,則系統轉為序列化另一個Java對象。上面代碼中序列化personbean對象,但是writeReplace方法返回ArrayList對象,所以序列化儲存的是ArrayList對象。那麼讀取儲存的序列化對象時擷取的當然也是ArrayList對象,所以Demo類代碼改為如下:
public class ObjectDemo { public static void main(String[] args) { PersonBean personBean1 = new PersonBean(); personBean1.setName("long"); personBean1.setAge(20); PersonBean personBean2 = new PersonBean(); personBean2.setName("fei"); personBean2.setAge(25); ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; String path = "D:\\Program Files (x86)\\ADT\\workspace\\JavaIO\\demoTest.txt"; try { FileOutputStream fileOutputStream = new FileOutputStream(path); FileInputStream fileInputStream = new FileInputStream(path); objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(personBean1); objectOutputStream.writeObject(personBean2); objectInputStream = new ObjectInputStream(fileInputStream);// PersonBean personBean3 = (PersonBean)objectInputStream.readObject();// PersonBean personBean4 = (PersonBean) objectInputStream.readObject(); ArrayList<Object> personBean3 = (ArrayList<Object>)objectInputStream.readObject(); ArrayList<Object> personBean4 = (ArrayList<Object>) objectInputStream.readObject(); System.out.println(personBean3.toString()); System.out.println(personBean4.toString()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }finally{ if (objectOutputStream != null) { try { objectOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
結果:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException
該方法可以實現保護性複製整個對象。該方法在序列化單例類、枚舉類時尤其有用。大家想想為什嗎??
原因在於,單例類和枚舉類希望還原序列化之後返回的還是原來的對象,但是在不實用該方法的情況下,還原序列化之後放回的是原來的對象的複製!!不是原來的對象!該方法就可以解決這個問題,返回原來序列化的同一個對象!!是不是很強大!
- 六
另一種實現自訂序列化的機制是使用Externalizable介面,實現該介面需要實現兩個方法
void readExternal(ObjectInput in)
void writeExternal(ObjectOutput out)
這兩個方法和簽名使用readObject writeObject方法非常相似,只是Externalizable介面強制使用自訂序列化!除了重寫這兩個方法之外,其他動作都一樣,也就不舉例子了。。
七
關於序列化機制 需要注意的幾點:
1、對象的類名、Field都會被序列化,方法、static field、transient field都不會被序列化
2、實現Serializable介面使用transient修飾符修飾field可以不被序列化,雖然static 修飾符可以達到同樣的效果,但是static transient不能混用。
3、還原序列化時必須有序列化對象的class檔案!!!
4、當通過檔案、網路來讀取序列化後的對象時,必須按實際寫入的順序讀取。
八
Java序列化機制允許為序列化類別提供一個private static final 的serialVersionUID值,該值用於標識該類的序列化版本。如果一個類升級了,只要它的serialVersionUID Field值保持不變,序列化機制也會把他們當成一個序列化版本。
為了在還原序列化時確保序列化版本的相容性,最好在每個要序列化的類中加入private static final long serialVersionUID這個field,它的具體值可以自己定義。
不顯式指定serialVersionUIR Field值的一個壞處,serialVersionUID值有JVM根據類的相關資訊計算,而修改後的類的計算結果與修改前的類的計算結果往往不同,從而造成對象的還原序列化因為類版本的不相容而失敗。
另外一個壞處是,不利於程式在不用的JVM之間移植。因為不同的編譯器計算該值的計算策略可能不同,從而造成雖然累完全沒有改變,但是因為JVM不同,也會出現序列化版本不相容而無法正確還原序列化的現象。
有這麼集中情況需要討論:
1、如果修改類是僅僅修改了方法,則還原序列化不受影響。無須指定serialVersionUID值
2、如果修改類時僅僅修改了靜態Field或瞬態Field,則還原序列化不受影響。無須指定serialVersionUID值。
3、如果修改類時修改了非靜態Field 非瞬態Field,則可能導致序列化版本不相容。如果物件流程中的對象和新類中包含同名的Field,而類型不同,則還原序列化失敗,類定義應該更新serialVersionUID值。如果物件流程中包含比新類中更多的Field,則多出的Field值被忽略,序列化版本可以相容,類定義可以不更新serialVersionUID值。如果新類比物件流程中的對象包含更多的Field,則序列化版本也可以相容,不用指定serialVersionUID值,但還原序列化得到的新類的對象中多出的Field值為null或者0。
源碼下載
Java 對象序列化詳解以及執行個體實現和源碼下載