Java 序列化

Java序列化

Java允許我們在內存中創建可複用的Java對象,但一般情況下,只有當JVM處於運行時,這些對象纔可能存在;也即,這些對象的生命週期不會比JVM的生命週期更長。但在實際應用中,就可能要求在JVM停止運行之後能夠持久化指定的對象,並在將來重新讀取被保存的對象。

使用Java對象序列化,在保存對象時,會把其狀態(只是對象狀態,不包括類變量)保存爲一組字節,以便再將這些字節組裝成對象。

除了在持久化對象時會用到對象序列化之外,當使用RMI,或在網絡中傳遞對象時,都會用到對象序列化。

1、       Serializable接口

在Java中,只要一個類實現了Java.io.Serializable接口,那麼它就可以被序列化,如下

/*枚舉默認繼承java.lang.Enum,而該類實現了Serializable接口

 * */

public enum Gender

{

    FEMALE,

    MALE,

    OTHER

}

 

public class Person   implements Serializable

{

  

   private static final long   serialVersionUID   = 1L;

  

   private int id;

   private Stringname;

   private int age;

   private Gendergender;

   private Stringinfo;

  

   public Person()

   {

     

   }  

/*省略settergetter方法

 * */

}

爲什麼一個類實現了Serializable接口,就可以被序列化呢?其實底層採用的是ObjectOutputStream類來進行序列化的,其中ObjectOutputStream類中最重要的一個方法爲writeObject0,部分代碼人如下:

private void writeObject0(Object obj, boolean unshared)

        throws IOException

{

            // …      

            // remaining cases

if (objinstanceof String)

{

                writeString((String)obj, unshared);

}

else if (cl.isArray())

{

                writeArray(obj,desc, unshared);

}

else if (obj instanceof Enum)

{

                writeEnum((Enum)obj, desc, unshared);

}

else if (obj instanceof Serializable)

{

                writeOrdinaryObject(obj,desc, unshared);

}

else

{

     // …   

            }

    }

2、       持久化對象

對於實現Serializable接口的類的實例,可以將對象持久化到磁盤中,在持久化時需要用到ObjectOutputStream流,如下

/*持久化*/

       File file=new File("/Users/ssl/person.out");

       OutputStream outputStream=new FileOutputStream(file);

       ObjectOutputStream objectOutputStream=newObjectOutputStream(outputStream);

       

       Person person=new Person();

       person.setId(1);

       person.setName("ssl");

       person.setAge(18);

       person.setGender(Gender.MALE);

       person.setInfo("ssl");

       

       objectOutputStream.writeObject(person);

       objectOutputStream.close();

       

       /*反序列化*/

       ObjectInputStream objectInputStream=newObjectInputStream(new FileInputStream(file));

       Person p=(Person)objectInputStream.readObject();

       objectInputStream.close();

       

    System.out.println(p);

 

3、       默認序列化機制

如果僅僅只是讓某個實現類實現Serializable接口,而沒有其他任何處理的話,則就是使用默認序列化機制。使用默認序列化機制,在序列化對象時,不僅會序列化當前對象本身,還會對該對象引用的其他對象也進行序列化(若屬性對象沒有實現Serializable接口,將會報錯),同樣地,若引用對象也引用其他的對象,這些對象都會被序列化。所以,如果一個對象包含的成員變量是容器類對象,而這些容器所含的元素也是容器類,那麼序列化的過程就會複雜,開銷也大。

此外,如果父類實現了Serializable接口,其子類都可以被序列化;若子類實現了Serializable接口,而父類沒有,則父類中的屬性不能被序列化,子類中的屬性仍能夠被序列化,結果導致父類的信息丟失。在純Java環境下,Java序列換能夠很好的工作,但是在跨平臺的系統中,最好採用通用存儲數據結構,如JSON或XML等。


3.1、transient

若是某個屬性不想被序列化,則可以加上transient關鍵字。默認序列化機制就會忽略該字段。

3.2、writeObject、readObject

在序列化和反序列化過程中會調用到writeObjectreadObject方法,這兩個方法爲私有方法,在序列化和反序列化過程中會通過反射機制來調用。所以在序列化類中添加這些方法,可以改變默認的序列化機制

public class Person   implements Serializable

{

  

   private static final long   serialVersionUID   = 1L;

  

   private int id;

   private Stringname;

   transient private int age;

   private Gendergender;

   private Stringinfo;

  

   public Person()

   {

     

   }

 

/*添加writeObjectreadObject方法會影響默認的序列化機制*/

private void writeObject(ObjectOutputStream out)throws IOException

{

/*defaultWriteObject會執行默認的序列化機制*/

   out.defaultWriteObject();

   out.writeInt(age);

}

 

private void readObject(ObjectInputStream in)throwsClassNotFoundException, IOException

{

  in.defaultReadObject();

  age=in.readInt();

}

 

}

3.3、Externalizable接口

無論是使用transient關鍵字,還是使用writeObject和readObject方法,其實都是基於Serializable接口的序列化。JDK還提供了另一個序列化接口-Externalizable,使用該接口之後,之前基於Serializable接口的序列化機制就將失效。

Externalizable繼承Serializable,當使用該接口時,序列化的細節需要由程序員顯示完成,重寫

public void writeExternal(ObjectOutput out)throws IOException

public void readExternal(ObjectInput in)throws IOException,

      ClassNotFoundException

等方法來完成序列化操作。

當使用Externalizable進行序列化時,讀取對象時,會調用序列化類無參的構造函數來創建一個新的對象,然後再將被保存對象的字段的值填充到新對象中,所以序列化類最好要提供無參的構造函數(默認的構造函數)。

 

3.4、readResolve

當我們使用單例模式時,應該是期望某個類的實例是唯一的,但如果該類是可序列化的,那麼情況可能會略有不同。

當從文件中反序列化得到的對象與原單例對象並不是一個對象,爲了能在序列化過程中仍保持單例的特性,可以在單例類中添加一個readResolve()方法,在該方法中直接返回Person的單例對象。

private Object readResolve();

無論是實現Serializable接口,或者Externalizable接口,當從I/O流中讀取對象時,readResolve()方法都會被調用到,實際上就是用readResolve()方法返回的對象直接替換在反序列化過程中創建的對象

4、       高級認識

將Java對象序列化爲二進制文件,是Java序列化的本質。在不部分情況下,開發人員只需要瞭解被序列化的類實現Serializable接口,使用ObjectInputStream和ObjectOutputStream進行對象的讀寫。然而,在某些情況下,知道這些是遠遠不夠的,下面列舉一些Java序列化中的高級知識。

4.1、序列化ID

序列化可以使Java對象在網絡中傳輸,在A端序列化的對象經過網絡傳向B端,在B端反序列化爲對象,此時要求A段和B段都有被序列化的類或.class文件。若此時,被序列化的類中沒有顯示指定序列化ID會出現什麼情況呢?

虛擬機是否允許序列化,不僅取決於類路徑和功能代碼是否一致,一個非常重要的一點的是兩個類的序列化ID是否一致

private static final long serialVersionUID   =1L;

若兩個類的序列化ID不一致,他們之間是無法相互序列化和反序列化的。簡單來說,Java的序列化機制是通過在運行時判斷類的序列化ID來驗證版本一致性的。在進行反序列化時,JVM會把傳來字節流中的序列化ID與本地對應的類中序列化ID進行比較。如果相同就認爲是一致的,可以進行反序列化;否則就會出現序列化版本不一致的異常。

4.2、靜態變量

在序列化時,並不持久化靜態變量。那麼,在本地序列化和反序列化時,如何獲取靜態變量?在網絡傳輸時,又如何獲取靜態變量。

 

4.3、敏感字段加密

在序列化過程中,虛擬機會試圖調用類對象中的writeObject和readObject方法,進行用戶自定義的序列化和反序列化。如果沒有這些方法,則默認會調用ObjectOutputStream的defaultWriteObject方法以及ObjectInputStream的defaultReadObject方法。基於該過程,我們自定義writeObject和readObject方法,來改變序列化的數值,如用於敏感字段的加密工作。

4.4、序列化存儲規則

Java序列化機制爲了節省磁盤空間,具有特定的存儲規則。當寫入文件的爲同一個對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用。反序列化時,恢復引用關係。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章