序列化機制——Serializable工作原理

序列化與反序列化的概念

從廣義上講,數據序列化就是將數據結構或者是對象轉換成我們可以存儲或者傳輸的數據格式的一個過程,在序列化的過程中,數據結構或者對象將其狀態信息寫入到臨時或者持久性的存儲區中,而在對應的反序列化過程中,則可以說是生成的數據被還原成數據結構或對象的過程。

在對象序列化和反序列化角度來看,Java 提供了 Serializable 接口,而 Android 提供特有的 Parcelable 接口。從廣義的角度來看,Json ,XML,SharedPreference,SQLite 等都是屬於序列化的範疇。

序列化與反序列化的目的

序列化:得到的字節序列可以方便在網絡上傳輸或者持久化——編碼。

反序列化:主要從網絡/磁盤讀取讀取字節序列然後轉化爲對象或者數據結構。——解碼

幾種常見的序列化和反序列化協議

幾種常見的序列化和反序列化協議

  • XML&SOAP

XML 是一種常用的序列化和反序列化協議,具有跨機器,跨語言等優點,SOAP(Simple Object Access protocol) 是一種被廣泛應用的,基於 XML 爲序列化和反序列化協議的結構化消息傳遞協議

  • JSON(Javascript Object Notation)

  • Protobuf

序列化方案

Serializable 接口

Serializable 接口

是 Java 提供的序列化接口,它是一個空接口:

public interface Serializable {
}

Serializable 用來標識當前類可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。

Serializable 接口的基本使用

通過 ObjectOutputStream 將需要序列化數據寫入到流中,因爲 Java IO 是一種裝飾者模式,因此可以通過 ObjectOutStream 包裝 FileOutStream 將數據寫入到文件中或者包裝 ByteArrayOutStream 將數據寫入到內存中。同理,可以通過 ObjectInputStream 將數據從磁盤 FileInputStream 或者內存 ByteArrayInputStream 讀取出來然後轉化爲指定的對象即可。

Serializable 接口的特點

Serializable 接口的特點

1. 序列化類的屬性沒有實現 Serializable 那麼在序列化就會報錯

具體可以跟進 ObjectOutputStream#writeObject() 源碼查看具體原因:

Exception in thread "main" java.io.NotSerializableException: com.example.seriable.Color
public class Student implements Serializable {
  private String name;
  private int age;
  /**
  * Color 類也是需要實現序列化接口的。
  */
  private Color color;//這裏如果沒有實現序列化接口,那麼在 Student 對象序列化時將會報錯
}
2. 在反序列化過程中,它的父類如果沒有實現序列化接口,那麼將需要提供無參構造函數來重新創建對象。
  • Animal 是父類,它沒有實現 Serilizable 接口
public class Animal {
    private String color;

    public Animal() {//沒有無參構造將會報錯
        System.out.println("調用 Animal 無參構造");
    }

    public Animal(String color) {
        this.color = color;

            System.out.println("調用 Animal 有 color 參數的構造");
    }

    @Override
    public String toString() {
        return "Animal{" +
                "color='" + color + '\'' +
                '}';
    }
}
  • BlackCat 是 Animal 的子類
public class BlackCat extends Animal implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;

    public BlackCat() {
        super();
        System.out.println("調用黑貓的無參構造");
    }

    public BlackCat(String color, String name) {
        super(color);
        this.name = name;
        System.out.println("調用黑貓有 color 參數的構造");
    }

    @Override
    public String toString() {
        return "BlackCat{" +
                "name='" + name + '\'' +super.toString() +'\'' +
                '}';
    }
}
  • SuperMain 測試類
public class SuperMain {
    private static final String FILE_PATH = "./super.bin";

    public static void main(String[] args) throws Exception {
        serializeAnimal();
        deserializeAnimal();
    }

    private static void serializeAnimal() throws Exception {
        BlackCat black = new BlackCat("black", "我是黑貓");
        System.out.println("序列化前:"+black.toString());
        System.out.println("=================開始序列化================");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH));
        oos.writeObject(black);
        oos.flush();
        oos.close();
    }

    private static void deserializeAnimal() throws Exception {
        System.out.println("=================開始反序列化================");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
        BlackCat black = (BlackCat) ois.readObject();
        ois.close();
        System.out.println(black);
    }
}
  • 輸出結果
調用 Animal 有 color 參數的構造
調用黑貓有 color 參數的構造
序列化前:BlackCat{name='我是黑貓'Animal{color='black'}'}
=================開始序列化================
=================開始反序列化================
調用 Animal 無參構造
BlackCat{name='我是黑貓'Animal{color='null'}'}

從上面的執行結果來看,如果要序列化的對象的父類 Animal 沒有實現序列化接口,那麼在反序列化時是會調用對應的無參構造方法的,這樣做的目的是重新初始化父類的屬性,例如 Animal 因爲沒有實現序列化接口,因此對應的 color 屬性就不會被序列化,因此反序列得到的 color 值就爲 null。

3. 一個實現 Serializable 接口的子類也是可以被序列化的。
4. 靜態成員變量是不能被序列化

序列化是針對對象屬性的,而靜態成員變量是屬於類的。

5. transient 標識的對象成員變量不參與序列化

在下面這個栗子中,MyList 這個類定義了一個 arr 數組屬性,初始化的數組長度爲 100。在實際序列化時如果讓 arr 屬性參與序列化的話,那麼長度爲 100 的數組都會被序列化下來,但是我在數組中可能只存放 30 個數組而已,這明顯是不可理的,所以這裏就要自定義序列化過程啦,具體的做法是寫以下兩個 private 方法:

private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException

從這兩個方法的名字就可以看出分別是序列化寫入數據和反序列化讀取數據用的,那麼這兩個方法是在哪裏使用呢?其實在序列化和反序列化過程中會通過反射調用的,具體下面會分析這個過程哦。

現在來看看這個 transient 應用:

public class MyList implements Serializable {

    private String name;


    /*
    transient 表示該成員 arr 不需要被序列化
     */
    private transient Object[] arr;

    public MyList() {
    }

    public MyList(String name) {
        this.name = name;
        this.arr = new Object[100];
        /*
        給前面30個元素進行初始化
         */
        for (int i = 0; i < 30; i++) {
            this.arr[i] = i;
        }
    }

    @Override
    public String toString() {
        return "MyList{" +
                "name='" + name + '\'' +
                ", arr=" + Arrays.toString(arr) +
                '}';
    }


    //-------------------------- 自定義序列化反序列化 arr 元素 ------------------

    /**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     * instance is emitted (int), followed by all of its elements
     * (each an <tt>Object</tt>) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
        //執行 JVM 默認的序列化操作
        s.defaultWriteObject();


        //手動序列化 arr  前面30個元素
        for (int i = 0; i < 30; i++) {
            s.writeObject(arr[i]);
        }
    }

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {

        s.defaultReadObject();
        arr = new Object[30];

        // Read in all elements in the proper order.
        for (int i = 0; i < 30; i++) {
            arr[i] = s.readObject();
        }
    }
}

  • 測試
public class TransientMain {
    private static final String FILE_PATH = "./transient.bin";
    public static void main(String[] args) throws Exception {
        serializeMyList();

        deserializeMyList();
    }

    private static void serializeMyList() throws Exception {
        System.out.println("序列化...");
        MyList myList = new MyList("ArrayList");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH));
        oos.writeObject(myList);
        oos.flush();
        oos.close();
    }

    /*
    1.如果 private  Object[] arr; 沒有使用 transient ,那麼整個數組都會被保存,而不是保存實際存儲的數據
    輸出結果:MyList{name='ArrayList', arr=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]}
    2.private transient Object[] arr;設置了 transient,表示 arr 元素不進行序列化
    輸出結果:MyList{name='ArrayList', arr=null}
    3.參考 ArrayList 處理內部的 transient Object[] elementData; 數組是通過 writeObject 和 readObject 實現的
    我們的 MyList 內部也可以借鑑這種方式實現transient元素的手動序列化和反序列化。
     */
    private static void deserializeMyList() throws Exception {
        System.out.println("反序列化...");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
        MyList myList = (MyList) ois.readObject();
        ois.close();
        System.out.println(myList);
    }
}

  • 測試輸出結果
序列化...
writeObject...
反序列化...
readObject...
MyList{name='ArrayList', arr=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]}
6.Serializable 在序列化和反序列化過程中大量使用了反射,因此其過程會產生的大量的內存碎片

serialVersionUID與兼容性問題

serialVersionUID與兼容性問題

這個 serialVersionUID 是什麼以及它的作用是什麼?

這個值是用於確保類序列化與反序列化的兼容性問題的,如果序列化和反序列化過程中這兩個值不一樣,那麼將導致序列化失敗,具體可以看下面的 serialVersionUID 兼容性問題。

如何生成這個 serialVersionUID呢?

  1. 使用 AS plugin 插件就可以生成
  2. 在JDK中,可以利用 JDK 的 bin 目錄下的 serialver 工具產生這個serialVersionUID,對於 Student.class,執行命令:serialver com.example.seriable.Student
➜  classes git:(master) ✗ /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/bin/serialver com.example.seriable.Student 
com.example.seriable.Student:    private static final long serialVersionUID = -6840182814363029482L;//這個就是工具生成的 SerialVersionUID 值了

使用 AS plugin 的方式應該底層也是使用到這個 JDK 工具去生成的 SerialVersionUID 值,測試結果來看這兩個生成的值是一樣的。

serialVersionUID 的兼容性問題是什麼?

serialVersionUID 的兼容性問題是什麼?

具體的兼容性問題如下:

java.io.InvalidClassException: com.example.seriable.Student; local class incompatible: stream classdesc
serialVersionUID = -926212341182608815, local class serialVersionUID = -6840182814363029482

關於這個異常,它是屬於兼容問題異常,是發生在反序列化階段,檢測到 serialVersionUID 不一致導致的。具體的分析如下:

序列化時使用的 serialVersionUID = -926212341182608815L,如果期間屬性被修改了,如果 serialVersionUID 發生改變 -6840182814363029482 ,那麼
反序列化時就會出現類不兼容問題。

serialVersionUID 發生改變有三種情況:

  1. 手動去修改導致當前的 serialVersionUID 與序列化前的不一樣。

  2. 我們根本就沒有手動去寫這個 serialVersionUID 常量,那麼 JVM 內部會根據類結構去計算得到這個 serialVersionUID 值,在類結構發生改變時(屬性增加,刪除或者類型修改了)這種也是會導致 serialVersionUID 發生變化。

  3. 假如類結構沒有發生改變,並且沒有定義 serialVersionUID ,但是反序列和序列化操作的虛擬機不一樣也可能導致計算出來的 serialVersionUID 不一樣。

JVM 規範強烈建議我們手動聲明一個版本號,這個數字可以是隨機的,只要固定不變就可以。同時最好是 private 和 final 的,儘量保證不變。

Externalizable 接口

Serializable 接口內部序列化是 JVM 自動實現的,如果我們想自定義序列化過程,就可以使用以上這個接口來實現,它內部提供兩個接口方法:

public interface Externalizable extends Serializable {
  	//將要序列化的對象屬性通過 var1.wrietXxx() 寫入到序列化流中
    void writeExternal(ObjectOutput var1) throws IOException;
		//將要反序列化的對象屬性通過 var1.readXxx() 讀出來
    void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException;
}
  • Externalizable 的使用
public class Person implements Externalizable {

    private static final long serialVersionUID = -7424420983806112577L;
    private String name;
    private int age;
    /*
    實現了Externalizable這個接口需要提供無參構造,在反序列化時會檢測
     */
    public Person() {
        System.out.println("Person: empty");
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("person writeExternal...");
        out.writeObject(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
        System.out.println("person readExternal...");

        name = (String) in.readObject();
        age = in.readInt();
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

  • 測試 Person 對象的序列化和反序列化
public class ExternalizableMain {
    private static final String FILE_PATH = "../person.bin";

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        Person person = new Person("zhangsan", 15);
        System.out.println(person.toString());
        serializable(person, FILE_PATH);
        System.out.println("============反序列化=============");
        person = (Person) deserializable(FILE_PATH);
        System.out.println(person.toString());
    }

    private static void serializable(Object o, String path) throws IOException {
        FileOutputStream boas = new FileOutputStream(path);
        ObjectOutputStream oos = new ObjectOutputStream(boas);
        oos.writeObject(o);
        oos.close();
        boas.close();
    }

    private static Object deserializable(String path) throws IOException, ClassNotFoundException {
        ObjectInputStream bis = new ObjectInputStream(new FileInputStream(path));
        Object obj = bis.readObject();
        return obj;
    }
}
  • 測試輸出結果:
Person{name='zhangsan', age=15}
person writeExternal...
============反序列化=============
Person: empty
person readExternal...
Person{name='zhangsan', age=15}

Java 的序列化步驟與數據結構分析

序列化算法一般會按步驟做如下事情:

  • 將對象實例相關的類元數據輸出。
  • 遞歸地輸出類的超類描述直到不再有超類。
  • 類元數據完了以後,開始從最頂層的超類開始輸出對象實例的實際數據值。
  • 從上至下遞歸輸出實例的數據

也許你看上面這幾個步驟會有點懵逼,不過實際的序列化過程就是按照上面的步驟進行的,看完這個就開始 writeObject 和 readObject 源碼解讀,而 writeObejct 的過程就是上面的4個步驟。

readObject/writeObject原理分析

writeObject 原理分析

  • ObjectOutputStream 構造函數
public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
    bout = new BlockDataOutputStream(out);//①
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);
    enableOverride = false;//②
    writeStreamHeader();//③
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
}

①bout:用於寫入一些類元數據還有對象中基本數據類型的值,在下面會分析。

②enableOverride :false 表示不支持重寫序列化過程,如果爲 true ,那麼需要重寫 writeObjectOverride 方法。這個一般不用管它。

③writeStreamHeader() 寫入頭信息,具體看下面分析。

  • ObjectOutputStream#writeStreamHeader()
protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);//①
    bout.writeShort(STREAM_VERSION);//②
}

①STREAM_MAGIC 聲明使用了序列化協議,bout 就是一個流,將對應的頭數據寫入該流中

②STREAM_VERSION 指定序列化協議版本

  • ObjectOUtStream#writeObject(obj);

上面是 ObjectOutStream 構造中做的事,下面來看看具體 writeObject 方法內部做了什麼事?

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {//一般不會走這裏,因爲在 ObjectOutputStream 構造設置爲 false 了
        writeObjectOverride(obj);
        return;
    }
    try {//代碼會執行這裏
        writeObject0(obj, false);
    } catch (IOException ex) {
        ...
    }
}
  • ObjectOutStream#writeObject0()
private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    ...
    try {
     
        Object orig = obj;
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc;
       
        //①
        desc = ObjectStreamClass.lookup(cl, true);
        ...
        //②
        if (obj instanceof Class) {
            writeClass((Class) obj, unshared);
        } else if (obj instanceof ObjectStreamClass) {
            writeClassDesc((ObjectStreamClass) obj, unshared);
        // END Android-changed:  Make Class and ObjectStreamClass replaceable.
        } else if (obj instanceof 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 {
        		//③
            if (extendedDebugInfo) {
                throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
            } else {
                throw new NotSerializableException(cl.getName());
            }
        }
    } 
    ...
}

① lookup 函數用於查找當前類的 ObjectStreamClass ,它是用於描述一個類的結構信息的,通過它就可以獲取對象及其對象屬性的相關信息,並且它內部持有該對象的父類的 ObjectStreamClass 實例。其內部大量使用了反射,讀者可以去看看這個類的源碼。

下面看看它的構造函數

private ObjectStreamClass(final Class<?> cl) {
    this.cl = cl;
    name = cl.getName();
    isProxy = Proxy.isProxyClass(cl);
    isEnum = Enum.class.isAssignableFrom(cl);
    serializable = Serializable.class.isAssignableFrom(cl);
    externalizable = Externalizable.class.isAssignableFrom(cl);
    Class<?> superCl = cl.getSuperclass();
  	//superDesc 表示需要序列化對象的父類的 ObjectStreamClass,如果爲空,則調用 lookUp 查找
    superDesc = (superCl != null) ? lookup(superCl, false) : null;
		//localDesc 表示自己
    localDesc = this;
		...
}

② 根據 obj 的類型去執行序列化操作,如果不符合序列化要求,那麼會③位置拋出 NotSerializableException 異常。

在上面描述過,如果一個需要序列化的對象的某個屬性沒有實現序列化接口,那麼就會此處拋出異常。讀者可以自行驗證。

  • ObjectOutputStream#writeOrdinaryObject
private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared)
    throws IOException
{
    ...
    try {
        desc.checkSerialize();
        //①
        bout.writeByte(TC_OBJECT);
        //②
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        //③
        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
        		//④
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}

①寫入類的元數據,TC_OBJECT. 聲明這是一個新的對象,如果寫入的是一個 String 類型的數據,那麼就需要 TC_STRING 這個標識。

②writeClassDesc 方法主要作用就是自上而下(從父類寫到子類,注意只會遍歷那些實現了序列化接口的類)寫入描述信息。該方法內部會不斷的遞歸調用,我們只需要關係這個方法是寫入描述信息就好了,讀者可以查閱一下源碼。

從這裏可以知道,序列化過程需要額外的寫入很多數據,例如描述信息,類數據等,因此序列化後佔用的空間肯定會更大。

③ desc.isExternalizable() 判斷需要序列化的對象是否實現了 Externalizable 接口,這個在上面已經演示過怎麼使用的,在序列化過程就是在這個地方進行判斷的。如果有,那麼序列化的過程就會由程序員自己控制了哦,writeExternalData 方法會回調,在這裏就可以愉快地編寫需要序列化的數據拉。

④ writeSerialData 在沒有實現 Externalizable 接口時,就執行這個方法

  • ObjectOutputstream#writeSerialData
private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
		//① 
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
    
        ObjectStreamClass slotDesc = slots[i].desc;
        
        if (slotDesc.hasWriteObjectMethod()) {//②
            PutFieldImpl oldPut = curPut;
            curPut = null;
            SerialCallbackContext oldContext = curContext;
            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "custom writeObject data (class \"" +
                    slotDesc.getName() + "\")");
            }
            try {
                curContext = new SerialCallbackContext(obj, slotDesc);
                bout.setBlockDataMode(true);
                slotDesc.invokeWriteObject(obj, this);
                bout.setBlockDataMode(false);
                bout.writeByte(TC_ENDBLOCKDATA);
            } finally {
                curContext.setUsed();
                curContext = oldContext;
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
            curPut = oldPut;
        } else {
            defaultWriteFields(obj, slotDesc);//③
        }
    }
}

① desc.getClassDataLayout 會返回 ObjectStreamClass.ClassDataSlot[] ,我們來看看 ClassDataSlot 類,可以看到它是封裝了 ObjectStreamClass 而已,所以我們就簡單的認爲 ① 這一步就是用於返回序列化對象及其父類的 ClassDataSlot[] 數組,我們可以從 ClassDataSlot 中獲取對應 ObjectStreamClass 描述信息。

static class ClassDataSlot {
    /** class descriptor "occupying" this slot */
    final ObjectStreamClass desc;
    /** true if serialized form includes data for this slot's descriptor */
    final boolean hasData;
    ClassDataSlot(ObjectStreamClass desc, boolean hasData) {
        this.desc = desc;
        this.hasData = hasData;
    }
}

② 開始遍歷返回的數組,slotDesc 這個我們就簡單將其看成對一個對象的描述吧。hasWriteObjectMethod 表示的是什麼呢?這個其實就是你要序列化這個對象是否有 writeObject 這個 private 方法,注意哦,這個方法並不是任何接口的方法,而是我們手動寫的,讀者可以參考 ArrayList 代碼,它內部就有這個方法。

那麼這個方法的作用是什麼呢?這個方法我們在上面也演示過具體的使用,它就是用於自定義序列化過程的,讀者可以返回到上面看看如果使用這個 writeObject 實現自定義序列化過程的。注意:其實這個過程不像實現 Externalizable 接口那樣,自己完全去自定義序列化數據。

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
    //執行 JVM 默認的序列化操作
    s.defaultWriteObject();
    //手動序列化 arr  前面30個元素
    for (int i = 0; i < 30; i++) {
        s.writeObject(arr[i]);
    }
}

③ defaultWriteFields 這個方法就是 JVM 自動幫我們序列化了,

private void defaultWriteFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();

    desc.checkDefaultSerialize();
    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    desc.getPrimFieldValues(obj, primVals);
    //①
    bout.write(primVals, 0, primDataSize, false);
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    desc.getObjFieldValues(obj, objVals);
    
    //②
    for (int i = 0; i < objVals.length; i++) {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                "field (class \"" + desc.getName() + "\", name: \"" +
                fields[numPrimFields + i].getName() + "\", type: \"" +
                fields[numPrimFields + i].getType() + "\")");
        }
        try {
            writeObject0(objVals[i],
                         fields[numPrimFields + i].isUnshared());
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
}

這個方法主要分爲以下兩步

  • ① 寫入基本數據類型的數據
  • ②寫入引用數據類型的數據,這裏最終又調用到了 writeObject0() 方法,讀者可以返回到上面去看看具體的實現。

好了,Serialzable 序列化的源碼分析就完成了。

readObject 原理分析

從流中讀取類的描述信息 ObjectStreamClass 實例,通過這個對象就可以創建出序列化的對象。

ObjectStreamClass desc = readClassDesc(false);
...
  Object obj;
try {
  	//創建對應反序列化的對象
    obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
    throw (IOException) new InvalidClassException(
        desc.forClass().getName(),
        "unable to create instance").initCause(ex);
}

讀取該對象及其對象的父類的 ObjectStreamClass信息

ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();

然後遍歷得到每一個 ObjectStreamClass 對象,將對應的屬性值賦值給需要反序列化的對象。

private void defaultReadFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
    }
    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    bin.readFully(primVals, 0, primDataSize, false);
    if (obj != null) {
        desc.setPrimFieldValues(obj, primVals);
    }
    int objHandle = passHandle;
    //從 ObjectStreamClass 中得到對象的所有 Field 信息
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    for (int i = 0; i < objVals.length; i++) {
        ObjectStreamField f = fields[numPrimFields + i];
        objVals[i] = readObject0(f.isUnshared());
        if (f.getField() != null) {
            handles.markDependency(objHandle, passHandle);
        }
    }
    if (obj != null) {
    		//將數據保存到對象中去
        desc.setObjFieldValues(obj, objVals);
    }
    passHandle = objHandle;
}

好了,以上就是基本的反序列化 readObject 的過程,這個過程基本是跟 writeObject 差不多,因此簡單的列舉了關鍵步驟而已。

項目地址:https://github.com/liaowjcoder/study4Java/tree/master/10_seriable

本文是筆者學習之後的總結,方便日後查看學習,有任何不對的地方請指正。

記錄於 2019年5月12號

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