天天看點

Java對象的序列化/反序列化原理及源碼解析(上)1 什麼是序列化、反序列化2 為什麼需要序列化?3 如何使用?4 序列化原理

https://github.com/Wasabi1234/Java-Interview-Tutorial

1 什麼是序列化、反序列化

Java序列化是指把Java對象儲存為二進制位元組碼的過程,Java反序列化是指把二進制碼重新轉換成Java對象的過程。

2 為什麼需要序列化?

2.1 使用場景

2.1.1 持久化對象

JVM允許我們在記憶體中建立可複用的Java對象,但一般隻有當JVM處于運作時,這些對象才可能存在。即這些對象的生命周期不會比JVM的生命周期更長。

但在現實應用中,可能要求在JVM停止運作之後能夠持久化指定對象,并在将來某時重新讀取被儲存的對象。Java對象序列化就能夠幫助我們實作該功能。使用Java對象序列化,在儲存對象時,會把其狀态儲存為一組位元組,後面再将這些位元組組裝成對象。

2.1.2 網絡傳輸

把Java對象通過網絡進行傳輸時。因為資料隻能以二進制形式在網絡中傳輸,是以當把對象通過網絡發出去前,需要先序列化成二進制資料,在接收端讀到二進制資料後反序列化成Java對象。

對象序列化儲存的是對象的”狀态”,即其成員變量,是以對象序列化不會關注靜态變量。 除了在持久化對象時會用到對象序列化,當使用RMI或在網絡中傳遞對象時,都會用到對象序列化。

Java序列化API為處理對象序列化提供了一個标準機制,該API簡單易用。

3 如何使用?

以序列化到檔案為例,看看Java序列化的基本用法。

package test;

import java.io.*;

public class SerializableTest {
    public static void main(String[] args) throws Exception {
        FileOutputStream fos = new FileOutputStream("temp.out");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        TestObject testObject = new TestObject();
        oos.writeObject(testObject);
        oos.flush();
        oos.close();

        FileInputStream fis = new FileInputStream("temp.out");
        ObjectInputStream ois = new ObjectInputStream(fis);
        TestObject deTest = (TestObject) ois.readObject();
        System.out.println(deTest.testValue);
        System.out.println(deTest.parentValue);
        System.out.println(deTest.innerObject.innerValue);
    }
}

class Parent implements Serializable {

    private static final long serialVersionUID = -4963266899668807475L;

    public int parentValue = 100;
}

class InnerObject implements Serializable {

    private static final long serialVersionUID = 5704957411985783570L;

    public int innerValue = 200;
}

class TestObject extends Parent implements Serializable {

    private static final long serialVersionUID = -3186721026267206914L;

    public int testValue = 300;

    public InnerObject innerObject = new InnerObject();
}      

程式執行完用sublime打開temp.out檔案,可以看到

aced 0005 7372 0017 636f 6d2e 7373 732e
7465 7374 2e54 6573 744f 626a 6563 74d3
c67e 1c4f 132a fe02 0002 4900 0974 6573
7456 616c 7565 4c00 0b69 6e6e 6572 4f62
6a65 6374 7400 1a4c 636f 6d2f 7373 732f
7465 7374 2f49 6e6e 6572 4f62 6a65 6374
3b78 7200 1363 6f6d 2e73 7373 2e74 6573
742e 5061 7265 6e74 bb1e ef0d 1fc9 50cd
0200 0149 000b 7061 7265 6e74 5661 6c75
6578 7000 0000 6400 0001 2c73 7200 1863
6f6d 2e73 7373 2e74 6573 742e 496e 6e65
724f 626a 6563 744f 2c14 8a40 24fb 1202
0001 4900 0a69 6e6e 6572 5661 6c75 6578
7000 0000 c8      

4 序列化原理

調用

ObjectOutputStream.writeObject()

ObjectInputStream.readObject()

後,究竟發生什麼?

4.1 ObjectStream Class

類的序列化描述符,該類可以描述需要被序列化的類的中繼資料,包括被序列化的類的名字以及序列号。可通過lookup()方法來查找、建立在這個JVM中加載的特定的ObjectStreamClass對象。

4.2 序列化 - writeObject()

調用wroteObject進行序列化前會先調用ObjectOutputStream的構造器,生成一個ObjectOutputStream對象:

public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
  // bout表示底層的位元組資料容器
  bout = new BlockDataOutputStream(out);
  handles = new HandleTable(10, (float) 3.00);
  subs = new ReplaceTable(10, (float) 3.00);
  enableOverride = false;
  // 寫入檔案頭
  writeStreamHeader();
  // flush資料
  bout.setBlockDataMode(true);
  if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
  } else {
        debugInfoStack = null;
  }
}      

構造器首先把bout綁定到底層的位元組資料容器,接着會調用writeStreamHeader:提供了writeStreamHeader方法,于是子類可将自己的頭部追加或添加到流中。 将魔數和版本寫入流中。

Java對象的序列化/反序列化原理及源碼解析(上)1 什麼是序列化、反序列化2 為什麼需要序列化?3 如何使用?4 序列化原理
Java對象的序列化/反序列化原理及源碼解析(上)1 什麼是序列化、反序列化2 為什麼需要序列化?3 如何使用?4 序列化原理

接着調用writeObject執行序列化:

将指定對象寫入ObjectOutputStream。 寫入對象的類,類的簽名以及該類及其所有父類的非transient和非靜态字段的值。 可以使用writeObject和readObject方法覆寫類的預設序列化。 該對象引用的對象是可傳遞的,是以ObjectInputStream可以重建對象的完整等效圖。

對于OutputStream的問題和不應序列化的類,将引發異常。 所有異常對于OutputStream都是緻命的,OutputStream處于不确定狀态,并且取決于調用者忽略還是恢複流狀态

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {
        writeObjectOverride(obj);
 return;  }
    try {
        // 調用writeObject0()進行序列化
        writeObject0(obj, false);
  } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
  }
        throw ex;
  }
}      

正常情況下會調用writeObject0()進行序列化:

基礎writeObject / writeUnshared實作

private void writeObject0(Object obj, boolean unshared) {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // 處理先前編寫的和不可替換的對象
            int h;
            if ((obj = subs.lookup(obj)) == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }

            // 檢查替換對象
            Object orig = obj;
            // 要序列化的對象的Class對象
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                 // 建立描述cl的ObjectStreamClass對象
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }

            // if object replaced, run through original checks a second time
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }

            // 根據實際的類型進行不同的寫入操作
            // remaining cases
            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) {
                // 被序列化對象實作了Serializable接口
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }      

生成一個描述被序列化對象類的類元資訊的ObjectStreamClass對象

根據傳入的需要序列化的對象的實際類型進行不同的序列化操作。從代碼裡面可以很明顯的看到,

對于String類型、數組類型和Enum可以直接進行序列化

如果被序列化對象實作了Serializable對象,則會調用writeOrdinaryObject()方法進行序列化

這裡可以解釋一個問題:Serializbale接口是個空的接口,并沒有定義任何方法,為什麼需要序列化的接口隻要實作Serializbale接口就能夠進行序列化。

答案是:Serializable接口這是一個辨別,告訴程式所有實作了”我”的對象都需要進行序列化。

是以,序列化過程接下來會執行到writeOrdinaryObject()這個方法中,該方法實作如下:

private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                (depth == 1 ? "root " : "") + "object (class \"" +
                obj.getClass().getName() + "\", " + obj.toString() + ")");
        }
        try {
            desc.checkSerialize();

            // 寫入Object标志位
            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,表示這是一個新的Object

/**
 * new Object.
 */
final static byte TC_OBJECT =       (byte)0x73;      

接下來會調用writeClassDesc()方法寫入被序列化對象的類的類中繼資料,writeClassDesc()方法實作如下:

private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    int handle;
    if (desc == null) {
        // 如果desc為null
        writeNull();
    } else if (!unshared && (handle = handles.lookup(desc)) != -1) {
        writeHandle(handle);
    } else if (desc.isProxy()) {
        writeProxyDesc(desc, unshared);
    } else {
        writeNonProxyDesc(desc, unshared);
    }
}      

在這個方法中會先判斷傳入的desc是否為null,如果為null則調用writeNull()方法

private void writeNull() throws IOException {
    // TC_NULL =         (byte)0x70;
    // 表示對一個Object引用的描述的結束
    bout.writeByte(TC_NULL);
}      

如果不為null,則一般情況下接下來會調用writeNonProxyDesc()方法,該方法實作如下:

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    // TC_CLASSDESC =    (byte)0x72;
    // 表示一個新的Class描述符
    bout.writeByte(TC_CLASSDESC);
    handles.assign(unshared ? null : desc);
 
    if (protocol == PROTOCOL_VERSION_1) {
        // do not invoke class descriptor write hook with old protocol
        desc.writeNonProxy(this);
    } else {
        writeClassDescriptor(desc);
    }
 
    Class cl = desc.forClass();
    bout.setBlockDataMode(true);
    if (cl != null && isCustomSubclass()) {
        ReflectUtil.checkPackageAccess(cl);
    }
    annotateClass(cl);
    bout.setBlockDataMode(false);
    bout.writeByte(TC_ENDBLOCKDATA);
 
    writeClassDesc(desc.getSuperDesc(), false);
}      

在這個方法中首先會寫入一個位元組的TC_CLASSDESC,這個位元組表示接下來的資料是一個新的Class描述符,接着會調用writeNonProxy()方法寫入實際的類元資訊,writeNonProxy()實作如下:

void writeNonProxy(ObjectOutputStream out) throws IOException {
    out.writeUTF(name); // 寫入類的名字
    out.writeLong(getSerialVersionUID()); // 寫入類的序列号
 
    byte flags = 0;
    // 擷取類的辨別
    if (externalizable) {
        flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
        int protocol = out.getProtocolVersion();
        if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
            flags |= ObjectStreamConstants.SC_BLOCK_DATA;
        }
    } else if (serializable) {
        flags |= ObjectStreamConstants.SC_SERIALIZABLE;
    }
    if (hasWriteObjectData) {
        flags |= ObjectStreamConstants.SC_WRITE_METHOD;
    }
    if (isEnum) {
        flags |= ObjectStreamConstants.SC_ENUM;
    }
    out.writeByte(flags); // 寫入類的flag
 
    out.writeShort(fields.length); // 寫入對象的字段的個數
    for (int i = 0; i < fields.length; i++) {
        ObjectStreamField f = fields[i];
        out.writeByte(f.getTypeCode());
        out.writeUTF(f.getName());
        if (!f.isPrimitive()) {
            // 如果不是原始類型,即是對象或者Interface
            // 則會寫入表示對象或者類的類型字元串
            out.writeTypeString(f.getTypeString());
        }
    }
}