java序列化反序列化深刻探究

何時須要序列化和反序列化

簡單的寫一個hello world程序,用不到序列化和反序列化。寫一個排序算法也用不到序列化和反序列化。可是當你想要將一個對象進行持久化寫入文件,或者你想將一個對象從一個網絡地址經過網絡協議發送到另外一個網絡地址時,這時候就須要考慮序列化和反序列化了。另外若是你想對一個對象實例進行深度拷貝,也能夠經過序列化和反序列化的方式進行。java

什麼是序列化和反序列化

Serialization-序列化:能夠看作是將一個對象轉化爲二進制流的過程算法

Deserialization-反序列化:能夠看作是將對象的二進制流從新讀取轉換成對象的過程網絡

怎麼實現序列化

只有實現了 Serializable 或 Externalizable 接口的類的對象才能被序列化,不然拋出異常。dom

Serializable接口

Serializable接口是Java提供的一個序列化接口,它是一個空接口,爲對象提供標準的序列化和反序列化操做。使用Serializable來實現的對象的序列化至關簡單,只須要在類的生命中指定一個相似相面的標識便可自動實現默認的序列化過程。
這種方式是Java提供的一種序列化方式,過程很是簡單,甚至有些開發人員都不須要聲明serialVersionUID也能夠完成這個過程,但serialVersionUID到底需不須要指定呢?
須要!
Java API既然提供了這個serialVersionUID,那麼它一定是有用的。這個serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化後的數據中的serialVersionUID只有和當前類的serialVersionUID相同纔可以正常地被反序列化。
serialVersionUID的詳細工做過程是這樣的:序列化的時候系統會把當前類的serialVersionUID寫入序列化的二進制文件中,當反序列化的時候系統會檢測文件中的serialVersionUID是否和當前類的serialVersionUID一致,若是一致就說明序列化的類的版本和當前類的版本是相同的,這個時候能夠成功反序列化;不然說明當前類和反序列化的類相比發生了某些變化,好比成員變量的數量、類型發生了變化,這個時候是沒法正常反序列化的。
通常來講,咱們應該手動指定serialVersionUID的值,好比1L,也可讓IDE根據當前類的結構自動去生成它的hash值,這樣序列化和反序列化時二者的serialVersionUID是相同的,所以能夠正常進行反序列化操做。若是不手動指定serialVersionUID的值反序列化時當前類有些改變,好比增長或者刪除了某些成員變量,那麼系統就會從新計算當前類的hash值並把它賦值給serialVersionUID,這個時候當前類的serialVersionUID就和反序列化數據中的serialVersionUID不一致,就會形成反序列化失敗的結果。因此,手動指定serialVersionUID能夠在很大程度上避免反序列化過程的失敗。好比當版本升級後,咱們可能刪除了某個成員變量也可能增長了一些新的成員變量,這個時候咱們的反序列化過程依然可以成功,程序仍然可以最大限度地回覆數據;相反,若是不指定serialVersionUID的話,程序會發生Crash。
固然,咱們還須要考慮一種狀況, 若是類結構發生了很是規改變,好比修改了類名,修改了成員變量的類型,這個時候儘管serialVersionUID驗證經過了,可是反序列化過程仍然會失敗,由於類的結構有了毀滅性的改變,根本沒法從老版本的數據中還原出一個新的類結構的對象。
 

對於實現了這兩個接口,具體序列化和反序列化的過程又分如下3種類狀況:ide

狀況1:若類僅僅實現了Serializable接口

ObjectOutputStream採用默認的序列化方式,對對象的非transient的實例變量進行序列化。
ObjcetInputStream採用默認的反序列化方式,對對象的非transient的實例變量進行反序列化。函數

狀況2:若類不只實現了Serializable接口,而且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out)。

ObjectOutputStream調用對象的writeObject(ObjectOutputStream out)的方法進行序列化。
ObjectInputStream會調用對象的readObject(ObjectInputStream in)的方法進行反序列化。ui

狀況3:若類實現了Externalnalizable接口,且類必須實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法。

ObjectOutputStream調用對象的writeExternal(ObjectOutput out))的方法進行序列化。
ObjectInputStream會調用對象的readExternal(ObjectInput in)的方法進行反序列化。this

 爲了進一步說明,咱們直接看jdk底層ArrayList的序列化和反序列化:spa

// 實現了Serializable接口,能夠被序列化
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
    // 實際元素被transient修飾,默認不會進行序列化
    private transient Object[] elementData;

    .....

    /**
     * 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{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

        // Write out array length
        s.writeInt(elementData.length);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);

    if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

    }
    
    /**
     * 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 {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

        // Read in array length and allocate array
        int arrayLength = s.readInt();
        Object[] a = elementData = new Object[arrayLength];

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

能夠看到,初看之下ArrayList的實際存儲元素不能被序列化。但實際上根據咱們上面的第二條原則,知道由於其重寫了writeObject和readObject方法,而在方法的內部實現了對具體存儲對象的序列化與反序列化。那麼這兩個方法到底是在何時執行的呢?咱們須要轉到ObjectOutputStream這個對象上來:debug

/**
 * Serialization's descriptor for classes.  It contains the name and
 * serialVersionUID of the class.  The ObjectStreamClass for a specific class
 * loaded in this Java VM can be found/created using the lookup method. 16  */
// 在序列化對象以前會封裝一個ObjectStreamClass對象
public class ObjectStreamClass implements Serializable  {
    /** class-defined writeObject method, or null if none */
    private Method writeObjectMethod;
    
     /**
     * Creates local class descriptor representing given class.
     */
    private ObjectStreamClass(final Class cl) { 36    
      ......
    if (serializable) {
        AccessController.doPrivileged(new PrivilegedAction() {
        public Object run() {
            if (isEnum) {
            suid = Long.valueOf(0);
            fields = NO_FIELDS;
            return null;
            }
            if (cl.isArray()) {
            fields = NO_FIELDS;
            return null;
            }
    
            suid = getDeclaredSUID(cl);
            try {
            fields = getSerialFields(cl);
            computeFieldOffsets();
            } catch (InvalidClassException e) {
            serializeEx = deserializeEx = e;
            fields = NO_FIELDS;
            }
            
            if (externalizable) {
            cons = getExternalizableConstructor(cl);
            } else {
            cons = getSerializableConstructor(cl);
                        // 其實就是writeObject方法
            writeObjectMethod = getPrivateMethod(cl, "writeObject", 
                new Class[] { ObjectOutputStream.class }, 
                Void.TYPE);
            readObjectMethod = getPrivateMethod(cl, "readObject", 
                new Class[] { ObjectInputStream.class }, 
                Void.TYPE);
            readObjectNoDataMethod = getPrivateMethod(
                cl, "readObjectNoData", null, Void.TYPE);
            hasWriteObjectData = (writeObjectMethod != null);
            }
            writeReplaceMethod = getInheritableMethod(
            cl, "writeReplace", null, Object.class);
            readResolveMethod = getInheritableMethod(
            cl, "readResolve", null, Object.class);
            return null;
        }
        });
    } else {
        suid = Long.valueOf(0);
        fields = NO_FIELDS;
    }

     .......107     }

    /**
     * Returns non-static private method with given signature defined by given
     * class, or null if none found.  Access checks are disabled on the
     * returned method (if any).
     */
    private static Method getPrivateMethod(Class cl, String name, 
                       Class[] argTypes,
                       Class returnType)
    {
    try {
        Method meth = cl.getDeclaredMethod(name, argTypes);
        meth.setAccessible(true);
        int mods = meth.getModifiers();
        return ((meth.getReturnType() == returnType) &&
            ((mods & Modifier.STATIC) == 0) &&
            ((mods & Modifier.PRIVATE) != 0)) ? meth : null;
    } catch (NoSuchMethodException ex) {
        return null;
    }
    }


     /**
     * Returns true if represented class is serializable (but not
     * externalizable) and defines a conformant writeObject method.  Otherwise,
     * returns false.
     */
    boolean hasWriteObjectMethod() {
    return (writeObjectMethod != null);
    }
}

public class ObjectOutputStream
    extends OutputStream implements ObjectOutput, ObjectStreamConstants
{
    /**
     * Magic number that is written to the stream header.
     */
    final static short STREAM_MAGIC = (short)0xaced;

    /**
     * Version number that is written to the stream header.
     */
    final static short STREAM_VERSION = 5;


    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;
        }   
    }

     protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
    }

    /**
     * Write the specified object to the ObjectOutputStream.  The class of the
     * object, the signature of the class, and the values of the non-transient
     * and non-static fields of the class and all of its supertypes are
     * written.  Default serialization for a class can be overridden using the
     * writeObject and the readObject methods.  Objects referenced by this
     * object are written transitively so that a complete equivalent graph of
     * objects can be reconstructed by an ObjectInputStream.196      */
    public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
        writeFatalException(ex);
        }
        throw ex;
    }
    }

     /**
     * Underlying writeObject/writeUnshared implementation.
     */
    private void writeObject0(Object obj, boolean unshared) 
    throws IOException 
    {
    boolean oldMode = bout.setBlockDataMode(false);
    depth++;
    try {
        // handle previously written and non-replaceable objects
       ......
        // check for replacement object
        ......241      261 
        // if object replaced, run through original checks a second time
       ......279 
        // 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) {
                // 若是不是特殊對象類型,最終會調用該方法
        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);
    }
    }   

    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();

        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();
        }  
        }
    }            

   /**
     * Writes instance data for each serializable class of given object, from
     * superclass to subclass.
     */
    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;
            // 若是重寫了序列化的方法writeObject,則調用對應的方法進行寫入,其實就是ObjectStreamClass 中的對應方法,能夠得出序列化的第2條規則
        if (slotDesc.hasWriteObjectMethod()) {
        PutFieldImpl oldPut = curPut;
        curPut = null;

        if (extendedDebugInfo) {
            debugInfoStack.push(
            "custom writeObject data (class \"" + 
            slotDesc.getName() + "\")");
        }

                SerialCallbackContext oldContext = curContext;
        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);
        }
    }
}

以上代碼就是分析序列化狀況2的實現,反序列化也能夠一樣跟蹤發現,這裏再也不重複。

總結序列化反序列化的注意點

1.父類實現了序列化,則子類自動實現了序列化,即子類不須要顯式實現Serializable接口,子類構造時會遞歸調用父類構造。
2.當父類沒有實現序列化,而子類須要實現時,子類須要顯式實現Serializable接口,而且父類中須要有無參的構造函數。
3.序列化只對對象的屬性進行保存,而不會保存其方法。
4.當類中的實例變量引用了其餘對象,那麼在對該類進行序列化時,引用的對象也會被序列化(須要這個引用的對象也實現Serializable接口,不然會出現 java.io.NotSerializableException)。
5.靜態成員變量屬於類不屬於對象,因此不參與序列化過程
6.用transient關鍵字標記的成員變量不參與序列化過程
相關文章
相關標籤/搜索