java中可定製的序列化過程 writeObject與readObject
阿新 • • 發佈:2019-01-02
在Java中使用Serialization相當簡單。如果你有一些物件想要進行序列化,你只需實現Serializable介面。然後,你可以使用ObjectOutputStream將該物件儲存至檔案或傳送到其他主機。所有的non-transient和non-static欄位都將被序列化,並且由反序列化重構造出一模一樣的物件聯絡圖(譬如許多引用都指向該物件)。但有時你可能想實現你自己的物件序列化和反序列化。那麼你可以在某些特定情形下得到更多的控制。來看下面的簡單例子。
Java程式碼
以下是序列化上述class到檔案和其反序列化的主函式。
Java程式碼
類SessionDTO展現的是要在兩個伺服器之間傳輸的session。它包含了一些資訊在欄位data上,該欄位需要被序列化。但是它還有另外一個欄位activationTime,該欄位應該是session物件第一次出現在任意伺服器上的時間。它不在我們想要傳輸的資訊之列。這個欄位應該在反序列化之後在賦值。進一步來說,沒必要把它放在stream中在伺服器中傳遞,因為它佔據了不必要的空間。
解決這種情況可以使用writeObject和readObject。有可能你們有一些人沒有聽說過它們,那是因為它們在許多Java書籍中給忽略了,而且它們們也不是眾多流行Java考試的一部分。讓我們用這些方法來重寫SessionDTO:
Java程式碼
方法writeObject處理物件的序列化。如果宣告該方法,它將會被ObjectOutputStream呼叫而不是預設的序列化程序。如果你是第一次看見它,你會很驚奇儘管它們被外部類呼叫但事實上這是兩個private的方法。並且它們既不存在於java.lang.Object,也沒有在Serializable中宣告。那麼ObjectOutputStream如何使用它們的呢?這個嗎,ObjectOutputStream使用了反射來尋找是否聲明瞭這兩個方法。因為ObjectOutputStream使用getPrivateMethod,所以這些方法不得不被宣告為priate以至於供ObjectOutputStream來使用。
在兩個方法的開始處,你會發現呼叫了defaultWriteObject()和defaultReadObject()。它們做的是預設的序列化程序,就像寫/讀所有的non-transient和 non-static欄位(但他們不會去做serialVersionUID的檢查).通常說來,所有我們想要自己處理的欄位都應該宣告為transient。這樣的話,defaultWriteObject/defaultReadObject便可以專注於其餘欄位,而我們則可為這些特定的欄位(譯者:指transient)定製序列化。使用那兩個預設的方法並不是強制的,而是給予了處理複雜應用時更多的靈活性。
你可以從這裡或這裡讀到更多有關於序列化的知識。
自己再補充一些:
1.Write的順序和read的順序需要對應,譬如有多個欄位都用wirteInt一一寫入流中,那麼readInt需要按照順序將其賦值;
2.Externalizable,該介面是繼承於Serializable ,所以實現序列化有兩種方式。區別在於Externalizable多聲明瞭兩個方法readExternal和writeExternal,子類必須實現二者。Serializable是內建支援的也就是直接implement即可,但Externalizable的實現類必須提供readExternal和writeExternal實現。對於Serializable來說,Java自己建立物件圖和欄位進行物件序列化,可能會佔用更多空間。而Externalizable則完全需要程式設計師自己控制如何寫/讀,麻煩但可以有效控制序列化的儲存的內容。
3.正如Effectvie Java中提到的,序列化就如同另外一個建構函式,只不過是有由stream進行建立的。如果欄位有一些條件限制的,特別是非可變的類定義了可變的欄位會反序列化可能會有問題。可以在readObject方法中新增條件限制,也可以在readResolve中做。參考56條“保護性的編寫readObject”和“提供一個readResolve方法”。
4.當有非常複雜的物件需要提供deep clone時,可以考慮將其宣告為可序列化,不過缺點也顯而易見,效能開銷。
Java程式碼
- class SessionDTO implements Serializable {
- private static final long serialVersionUID = 1L;
- private int data; // Stores session data
- // Session activation time (creation, deserialization)
- private long activationTime;
- public SessionDTO(int data) {
-
this
- this.activationTime = System.currentTimeMillis();
- }
- public int getData() {
- return data;
- }
- public long getActivationTime() {
- return activationTime;
- }
- }
以下是序列化上述class到檔案和其反序列化的主函式。
Java程式碼
-
public class SerializeTester implements
- public static void main(String... strings) throws Exception {
- File file = new File("out.ser");
- ObjectOutputStream oos = new ObjectOutputStream(
- new FileOutputStream(file));
- SessionDTO dto = new SessionDTO(1);
-
oos.writeObject(dto);
- oos.close();
- ObjectInputStream ois = new ObjectInputStream(
- new FileInputStream(file));
- SessionDTO dto = (SessionDTO) ois.readObject();
- System.out.println("data : " + dto.getData()
- + " activation time : " + dto.getActivationTime());
- ois.close();
- }
- }
類SessionDTO展現的是要在兩個伺服器之間傳輸的session。它包含了一些資訊在欄位data上,該欄位需要被序列化。但是它還有另外一個欄位activationTime,該欄位應該是session物件第一次出現在任意伺服器上的時間。它不在我們想要傳輸的資訊之列。這個欄位應該在反序列化之後在賦值。進一步來說,沒必要把它放在stream中在伺服器中傳遞,因為它佔據了不必要的空間。
解決這種情況可以使用writeObject和readObject。有可能你們有一些人沒有聽說過它們,那是因為它們在許多Java書籍中給忽略了,而且它們們也不是眾多流行Java考試的一部分。讓我們用這些方法來重寫SessionDTO:
Java程式碼
- class SessionDTO implements Serializable {
- private static final long serialVersionUID = 1L;
- private transient int data; // Stores session data
- //Session activation time (creation, deserialization)
- private transient long activationTime;
- public SessionDTO(int data) {
- this.data = data;
- this.activationTime = System.currentTimeMillis();
- }
- private void writeObject(ObjectOutputStream oos) throws IOException {
- oos.defaultWriteObject();
- oos.writeInt(data);
- System.out.println("session serialized");
- }
- private void readObject(ObjectInputStream ois) throws IOException,
- ClassNotFoundException {
- ois.defaultReadObject();
- data = ois.readInt();
- activationTime = System.currentTimeMillis();
- System.out.println("session deserialized");
- }
- public int getData() {
- return data;
- }
- public long getActivationTime() {
- return activationTime;
- }
- }
方法writeObject處理物件的序列化。如果宣告該方法,它將會被ObjectOutputStream呼叫而不是預設的序列化程序。如果你是第一次看見它,你會很驚奇儘管它們被外部類呼叫但事實上這是兩個private的方法。並且它們既不存在於java.lang.Object,也沒有在Serializable中宣告。那麼ObjectOutputStream如何使用它們的呢?這個嗎,ObjectOutputStream使用了反射來尋找是否聲明瞭這兩個方法。因為ObjectOutputStream使用getPrivateMethod,所以這些方法不得不被宣告為priate以至於供ObjectOutputStream來使用。
在兩個方法的開始處,你會發現呼叫了defaultWriteObject()和defaultReadObject()。它們做的是預設的序列化程序,就像寫/讀所有的non-transient和 non-static欄位(但他們不會去做serialVersionUID的檢查).通常說來,所有我們想要自己處理的欄位都應該宣告為transient。這樣的話,defaultWriteObject/defaultReadObject便可以專注於其餘欄位,而我們則可為這些特定的欄位(譯者:指transient)定製序列化。使用那兩個預設的方法並不是強制的,而是給予了處理複雜應用時更多的靈活性。
你可以從這裡或這裡讀到更多有關於序列化的知識。
自己再補充一些:
1.Write的順序和read的順序需要對應,譬如有多個欄位都用wirteInt一一寫入流中,那麼readInt需要按照順序將其賦值;
2.Externalizable,該介面是繼承於Serializable ,所以實現序列化有兩種方式。區別在於Externalizable多聲明瞭兩個方法readExternal和writeExternal,子類必須實現二者。Serializable是內建支援的也就是直接implement即可,但Externalizable的實現類必須提供readExternal和writeExternal實現。對於Serializable來說,Java自己建立物件圖和欄位進行物件序列化,可能會佔用更多空間。而Externalizable則完全需要程式設計師自己控制如何寫/讀,麻煩但可以有效控制序列化的儲存的內容。
3.正如Effectvie Java中提到的,序列化就如同另外一個建構函式,只不過是有由stream進行建立的。如果欄位有一些條件限制的,特別是非可變的類定義了可變的欄位會反序列化可能會有問題。可以在readObject方法中新增條件限制,也可以在readResolve中做。參考56條“保護性的編寫readObject”和“提供一個readResolve方法”。
4.當有非常複雜的物件需要提供deep clone時,可以考慮將其宣告為可序列化,不過缺點也顯而易見,效能開銷。