Java序列化 ObjectInputStream源碼解析

上一篇講了類的序列化,今天要講類的反序列化,ObjectInputStream。

從內部變量中我們可以看出,內部包含一個塊輸入流,因爲有handle機制所以也有一個內部緩存表但不是hash表

    /** 處理數據塊轉換的過濾流 */
    private final BlockDataInputStream bin;
    /** 確認調用返回列表 */
    private final ValidationList vlist;
    /** 遞歸深度 */
    private long depth;
    /** 對任何種類的對象、類、枚舉、代理的引用總數 */
    private long totalObjectRefs;
    /** 流是否關閉 */
    private boolean closed;

    /** 線柄->obj/exception映射 */
    private final HandleTable handles;
    /** 傳遞句柄值上下調用棧的草稿區 */
    private int passHandle = NULL_HANDLE;
    /** 當在字段值末尾因沒有TC_ENDBLOCKDATA阻塞時設置的標誌 */
    private boolean defaultDataEnd = false;

    /** b讀取原始字段值的緩衝區 */
    private byte[] primVals;

    /** 如果爲true,調用readObjectOverride()來替代readObject() */
    private final boolean enableOverride;
    /** if true, invoke resolveObject()如果爲true調用resolveObject() */
    private boolean enableResolve;

    /**
     * 向上調用類定義的readObject方法期間的上下文,保持當前在被反序列化的對象和當前類的描述符。非readObject向上調用期間爲null。
     */
    private SerialCallbackContext curContext;

    /**
     * 類描述符過濾器和從流中讀取的類,可能是null
     */
    private ObjectInputFilter serialFilter;

從構造函數中可以看出,同樣需要驗證類的安全性,同時在初始化時會自動讀取Java序列化頭部信息。跟輸出流一樣,預留了一個生成全空的類輸入流初始化方法,用於繼承ObjectInputStream的子類進行重寫。安全驗證和輸出流中是相同的,而讀取頭部信息需要讀取魔數和版本號並驗證與當前JDK中的是否一致。當然,如果兩端使用的JDK中這個類版本號不一致就會出現異常。

    /**
     * 創建一個ObjectInputStream從指定的InputStream中讀取。一個序列化流頭部從這個流讀取和驗證。這個構造器會阻塞直到對應的ObjectOutputStream已經寫並刷新了頭部。
     * 如果安裝了安全管理器,這個構造器會在被重寫了ObjectInputStream.readFields或ObjectInputStream.readUnshared的子類構造器
     * 直接或間接調用的時候檢查enableSubclassImplementation序列化許可
     *
     * @param   in input stream to read from
     * @throws  StreamCorruptedException 如果流頭部錯誤
     * @throws  IOException 在讀取流頭部是發生了IO錯誤
     * @throws  SecurityException 如果不被信任的子類非法重寫了安全敏感方法
     * @throws  NullPointerException 如果in是null
     */
    public ObjectInputStream(InputStream in) throws IOException {
        verifySubclass();
        bin = new BlockDataInputStream(in);
        handles = new HandleTable(10);
        vlist = new ValidationList();
        serialFilter = ObjectInputFilter.Config.getSerialFilter();
        enableOverride = false;
        readStreamHeader();
        bin.setBlockDataMode(true);
    }

    protected ObjectInputStream() throws IOException, SecurityException {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
        bin = null;
        handles = null;
        vlist = null;
        serialFilter = ObjectInputFilter.Config.getSerialFilter();
        enableOverride = true;
    }

    protected void readStreamHeader()
        throws IOException, StreamCorruptedException
    {
        short s0 = bin.readShort();
        short s1 = bin.readShort();
        if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
            throw new StreamCorruptedException(
                String.format("invalid stream header: %04X%04X", s0, s1));
        }
    }

下面直接切入正題,來看一下readObject,讀和寫一樣也是final方法,所以子類要重寫必須通過readObjectOverride方法來完成。如果類裏面定義了非基本數據類型變量,需要進行嵌套讀取,outerHandle用來存儲上一層讀取對象的句柄,然後調用readObject0讀取生成對象。完成之後要記錄句柄依賴,並檢查有無異常產生。最上層讀取完成之後要發起回調。

    public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();//因爲是final方法,子類要重寫只能通過這裏
        }

        // 如果是嵌套讀取,passHandle包含封閉對象的句柄
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);//記錄句柄依賴
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();//最上層調用的讀取完成後,發起回調
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();//最上層調用退出時清除調用返回列表和句柄緩存
            }
        }
    }

readObject0必須在塊輸入流內爲空也就是上一次反序列化完全結束後才能開始,否則會拋出異常。首先,從流中讀取一個標誌位,判斷當前下一個內容是什麼類型,上次我們講到過,輸出的時候都會先輸出TC_OBJECT,所以在默認情況下也是先讀取到TC_OBJECT,也就是先執行readOrdinaryObject

    private Object readObject0(boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {//之前是塊輸入模式且有未消費的數據會拋出異常
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * 流當前在字段值通過默認序列化塊寫的末尾,因爲沒有終止TC_ENDBLOCKDATA標記,模擬通常數據結尾的行爲
                 */
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }

        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {//讀到流reset標誌
            bin.readByte();//消耗緩存的字節
            handleReset();//depth爲0時執行clear方法,否則拋出異常
        }

        depth++;//增加遞歸深度
        totalObjectRefs++;//增加引用對象數
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();

                case TC_REFERENCE:
                    return readHandle(unshared);

                case TC_CLASS:
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);

                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));

                case TC_ARRAY:
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));//一般會先進入這裏

                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);

                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }

                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

readOrdinaryObject是讀取一個自定義類最上層直接調用的方法。幾個關鍵的點,首先是要讀取類的描述信息,然後根據類描述符創建一個實例,將對象實例存儲到句柄緩存中並將句柄存儲到passHandle,然後讀取內部變量數據,最後檢查有沒有替換對象

    private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);//讀取類描述信息
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

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

        passHandle = handles.assign(unshared ? unsharedMarker : obj);//將對象存儲到句柄緩存中
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }
        //讀取序列化內部變量數據
        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);//標記句柄對應的對象讀取完成

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())//存在替換對象
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

那麼先來看下怎麼讀取類的描述信息readClassDesc,從上面方法的case中不能分析出,在沒有發生異常使用的情況下,此時的case只會進入TC_PROXYCLASSDESC或者TC_CLASSDESC也就是動態代理類的描述符合普通類的描述符。對於readProxyDesc讀取並返回一個動態代理類的類描述符,而readNonProxyDesc則是返回一個普通類的描述符,兩個方法都會設置passHandle到動態類描述符的設置句柄。如果類描述符不能被解析爲本地虛擬機中的一個類,一個ClassNotFoundException會被關聯到描述符的句柄中。關於動態代理和反射的部分下次要再仔細看一下。不過這裏又是讀取類描述,又是安全檢查還有類合法性檢查,跟直接在讀取端指定類相比額外多出很多開銷。

    private ObjectStreamClass readClassDesc(boolean unshared)
        throws IOException
    {
        byte tc = bin.peekByte();
        ObjectStreamClass descriptor;
        switch (tc) {
            case TC_NULL:
                descriptor = (ObjectStreamClass) readNull();
                break;
            case TC_REFERENCE:
                descriptor = (ObjectStreamClass) readHandle(unshared);
                break;
            case TC_PROXYCLASSDESC:
                descriptor = readProxyDesc(unshared);
                break;
            case TC_CLASSDESC:
                descriptor = readNonProxyDesc(unshared);
                break;
            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
        if (descriptor != null) {
            validateDescriptor(descriptor);
        }
        return descriptor;
    }
    
    private ObjectStreamClass readProxyDesc(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_PROXYCLASSDESC) {
            throw new InternalError();
        }

        ObjectStreamClass desc = new ObjectStreamClass();
        int descHandle = handles.assign(unshared ? unsharedMarker : desc);//添加描述符到句柄緩存中
        passHandle = NULL_HANDLE;

        int numIfaces = bin.readInt();//接口實現類數量
        if (numIfaces > 65535) {
            throw new InvalidObjectException("interface limit exceeded: "
                    + numIfaces);
        }
        String[] ifaces = new String[numIfaces];
        for (int i = 0; i < numIfaces; i++) {
            ifaces[i] = bin.readUTF();//讀取接口名
        }

        Class<?> cl = null;
        ClassNotFoundException resolveEx = null;
        bin.setBlockDataMode(true);
        try {
            if ((cl = resolveProxyClass(ifaces)) == null) {
                resolveEx = new ClassNotFoundException("null class");
            } else if (!Proxy.isProxyClass(cl)) {
                throw new InvalidClassException("Not a proxy");
            } else {
                //這個檢查等價於isCustomSubclass
                ReflectUtil.checkProxyPackageAccess(
                        getClass().getClassLoader(),
                        cl.getInterfaces());
                // Filter the interfaces過濾接口
                for (Class<?> clazz : cl.getInterfaces()) {
                    filterCheck(clazz, -1);
                }
            }
        } catch (ClassNotFoundException ex) {
            resolveEx = ex;
        }

        // Call filterCheck on the class before reading anything else
        filterCheck(cl, -1);

        skipCustomData();

        try {
            totalObjectRefs++;
            depth++;
            desc.initProxy(cl, resolveEx, readClassDesc(false));//初始化類描述符
        } finally {
            depth--;
        }

        handles.finish(descHandle);
        passHandle = descHandle;
        return desc;
    }
    
    private ObjectStreamClass readNonProxyDesc(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_CLASSDESC) {
            throw new InternalError();
        }

        ObjectStreamClass desc = new ObjectStreamClass();
        int descHandle = handles.assign(unshared ? unsharedMarker : desc);//將描述符存儲到句柄緩存中,返回句柄值
        passHandle = NULL_HANDLE;

        ObjectStreamClass readDesc = null;
        try {
            readDesc = readClassDescriptor();
        } catch (ClassNotFoundException ex) {
            throw (IOException) new InvalidClassException(
                "failed to read class descriptor").initCause(ex);
        }

        Class<?> cl = null;
        ClassNotFoundException resolveEx = null;
        bin.setBlockDataMode(true);
        final boolean checksRequired = isCustomSubclass();
        try {
            if ((cl = resolveClass(readDesc)) == null) {//通過類描述解析類
                resolveEx = new ClassNotFoundException("null class");
            } else if (checksRequired) {
                ReflectUtil.checkPackageAccess(cl);
            }
        } catch (ClassNotFoundException ex) {
            resolveEx = ex;
        }

        // Call filterCheck on the class before reading anything else
        filterCheck(cl, -1);

        skipCustomData();

        try {
            totalObjectRefs++;
            depth++;
            desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));//初始化類描述符
        } finally {
            depth--;
        }

        handles.finish(descHandle);
        passHandle = descHandle;

        return desc;
    }

接下來是讀取序列化內部變量數據,由於readExternalData除了一些安全性的判斷外,直接調用了類中的方法,所以就不看了,直接看readSerialData中默認的讀取數據部分defaultReadFields。注意到基本數據類型可以直接通過字節輸入流來進行讀取,而非基本數據類型則需要遞歸調用readObject0來讀取。

    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) {//從readSerialData進入這個方法時obj爲null
            desc.setPrimFieldValues(obj, primVals);
        }

        int objHandle = passHandle;
        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;
    }

然後看一下其他非基本數據類的讀取。readNull讀取null代碼並將句柄設置爲NULL_HANDLE,readString讀取UTF編碼,readArray需要先讀取數組長度,然後根據類的類型進行讀取。

    private Object readNull() throws IOException {
        if (bin.readByte() != TC_NULL) {
            throw new InternalError();
        }
        passHandle = NULL_HANDLE;
        return null;
    }

    private String readString(boolean unshared) throws IOException {
        String str;
        byte tc = bin.readByte();
        switch (tc) {
            case TC_STRING:
                str = bin.readUTF();
                break;

            case TC_LONGSTRING:
                str = bin.readLongUTF();
                break;

            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
        passHandle = handles.assign(unshared ? unsharedMarker : str);
        handles.finish(passHandle);
        return str;
    }

    private Object readArray(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ARRAY) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        int len = bin.readInt();

        filterCheck(desc.forClass(), len);

        Object array = null;
        Class<?> cl, ccl = null;
        if ((cl = desc.forClass()) != null) {
            ccl = cl.getComponentType();
            array = Array.newInstance(ccl, len);
        }

        int arrayHandle = handles.assign(unshared ? unsharedMarker : array);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(arrayHandle, resolveEx);
        }

        if (ccl == null) {
            for (int i = 0; i < len; i++) {
                readObject0(false);
            }
        } else if (ccl.isPrimitive()) {
            if (ccl == Integer.TYPE) {
                bin.readInts((int[]) array, 0, len);
            } else if (ccl == Byte.TYPE) {
                bin.readFully((byte[]) array, 0, len, true);
            } else if (ccl == Long.TYPE) {
                bin.readLongs((long[]) array, 0, len);
            } else if (ccl == Float.TYPE) {
                bin.readFloats((float[]) array, 0, len);
            } else if (ccl == Double.TYPE) {
                bin.readDoubles((double[]) array, 0, len);
            } else if (ccl == Short.TYPE) {
                bin.readShorts((short[]) array, 0, len);
            } else if (ccl == Character.TYPE) {
                bin.readChars((char[]) array, 0, len);
            } else if (ccl == Boolean.TYPE) {
                bin.readBooleans((boolean[]) array, 0, len);
            } else {
                throw new InternalError();
            }
        } else {
            Object[] oa = (Object[]) array;
            for (int i = 0; i < len; i++) {
                oa[i] = readObject0(false);
                handles.markDependency(arrayHandle, passHandle);
            }
        }

        handles.finish(arrayHandle);
        passHandle = arrayHandle;
        return array;
    }

readHandle方法從緩存中尋找該對象

    private Object readHandle(boolean unshared) throws IOException {
        if (bin.readByte() != TC_REFERENCE) {
            throw new InternalError();
        }
        passHandle = bin.readInt() - baseWireHandle;//因爲寫的時候handle值加上了baseWireHandle
        if (passHandle < 0 || passHandle >= handles.size()) {
            throw new StreamCorruptedException(
                String.format("invalid handle value: %08X", passHandle +
                baseWireHandle));
        }
        if (unshared) {
            // REMIND: what type of exception to throw here?
            throw new InvalidObjectException(
                "cannot read back reference as unshared");
        }

        Object obj = handles.lookupObject(passHandle);//緩存中尋找該對象
        if (obj == unsharedMarker) {
            // REMIND: what type of exception to throw here?
            throw new InvalidObjectException(
                "cannot read back reference to unshared object");
        }
        filterCheck(null, -1);       // just a check for number of references, depth, no class檢查引用數量,遞歸深度,是否有這個類
        return obj;
    }

BlockDataInputStream

跟輸出時相同,對象輸入流也使用的是塊輸入流,輸入流有兩個模式:默認模式下,輸入數據寫入的格式和DataOutputStream相同;在塊數據模式,輸入的數據被塊數據標記歸爲一類。邏輯上來說,讀和寫應該是相對應的,主要是要判斷讀取到的頭部信息,然後根據不同的信息來讀取實際信息。有些跟輸出部分非常重複的就跳過了

內部變量主要需要注意兩個不同的輸入流,din是塊輸入流,in是取數輸入流,in中的下層輸入流是最初在構造ObjectInputStream中傳入的變量,所以通常是FileInputStream。

        /** maximum data block length最大數據塊長度1K */
        private static final int MAX_BLOCK_SIZE = 1024;
        /** maximum data block header length最大數據塊頭部長度 */
        private static final int MAX_HEADER_SIZE = 5;
        /** (tunable) length of char buffer (for reading strings)可調節的字符緩衝的長度作爲供讀取的字符串 */
        private static final int CHAR_BUF_SIZE = 256;
        /** readBlockHeader() return value indicating header read may block readBlockHeader()返回的值指示頭部可能阻塞 */
        private static final int HEADER_BLOCKED = -2;

        /** buffer for reading general/block data讀取一般/塊數據的緩衝區 */
        private final byte[] buf = new byte[MAX_BLOCK_SIZE];
        /** buffer for reading block data headers讀取塊數據頭部的緩衝區 */
        private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
        /** char buffer for fast string reads快速字符串讀取的字符緩衝區 */
        private final char[] cbuf = new char[CHAR_BUF_SIZE];

        /** block data mode塊數據模式 */
        private boolean blkmode = false;

        // block data state fields; values meaningful only when blkmode true塊數據狀態字段,這些值僅在blkmode爲true時有意義
        /** current offset into buf當前進入buf的偏移 */
        private int pos = 0;
        /** end offset of valid data in buf, or -1 if no more block data buf中有效值的結束偏移,沒有更多塊數據buf時爲-1 */
        private int end = -1;
        /** number of bytes in current block yet to be read from stream 在當前block中還沒有從流中讀取的字節數 */
        private int unread = 0;

        /** underlying stream (wrapped in peekable filter stream) 下層流包裝在可見過濾流中 */
        private final PeekInputStream in;
        /** loopback stream (for data reads that span data blocks) 迴路流用於擴展數據塊的數據讀取 */
        private final DataInputStream din;

構造函數就是初始化兩個流變量

        BlockDataInputStream(InputStream in) {
            this.in = new PeekInputStream(in);
            din = new DataInputStream(this);
        }

setBlockDataMode將塊數據模式設爲給定的模式,true是打開,off是關閉,並返回之前的模式值。如果新的模式和舊模式一樣,什麼都不做。如果塊數據模式從打開被改爲關閉時有未消費的塊數據還留在流中,會拋出IllegalStateException

        boolean setBlockDataMode(boolean newmode) throws IOException {
            if (blkmode == newmode) {
                return blkmode;
            }
            if (newmode) {//從關閉到打開,重置塊數據參數狀態
                pos = 0;
                end = 0;
                unread = 0;
            } else if (pos < end) {//從打開到關閉且有未消費的塊數據
                throw new IllegalStateException("unread block data");
            }
            blkmode = newmode;
            return !blkmode;
        }

skipBlockData如果在塊數據模式,跳躍到數據塊的當前組的末尾但不會取消塊數據模式。如果不在塊數據模式,拋出IllegalStateException。這個方法調用了refill,refill用塊數據再裝滿內部緩衝區。任何buf中的數據在調用這個方法時被認爲是已經被消費了。設置pos、end、unread字段值來反映有效塊數據的數量。如果流中的下一個元素不是一個數據塊,設置pos=0和unread=0,end=-1

        void skipBlockData() throws IOException {
            if (!blkmode) {
                throw new IllegalStateException("not in block data mode");
            }
            while (end >= 0) {
                refill();
            }
        }

        private void refill() throws IOException {
            try {
                do {
                    pos = 0;
                    if (unread > 0) {//當前block中還有未讀取的字節
                        int n =
                            in.read(buf, 0, Math.min(unread, MAX_BLOCK_SIZE));//讀取unread和最大數據塊長度中的較小值的數據到buf中
                        if (n >= 0) {
                            end = n;//end爲讀取到的字節數
                            unread -= n;//當前block中還沒讀取的字節減少n
                        } else {
                            throw new StreamCorruptedException(
                                "unexpected EOF in middle of data block");
                        }
                    } else {
                        int n = readBlockHeader(true);
                        if (n >= 0) {
                            end = 0;
                            unread = n;
                        } else {
                            end = -1;
                            unread = 0;
                        }
                    }
                } while (pos == end);//成功讀取到數據就會退出循環
            } catch (IOException ex) {//出現異常說明下一個元素不是數據塊
                pos = 0;
                end = -1;
                unread = 0;
                throw ex;
            }
        }

readBlockHeader嘗試讀取下一個塊數據頭部。如果canBlock是false並且一個完整的頭部沒有阻塞時不能被讀取,返回HEADER_BLOCKED。否則如果流中下一個元素是一個塊數據頭部,返回頭部標識的這個塊數據的長度,否則返回-1

        private int readBlockHeader(boolean canBlock) throws IOException {
            if (defaultDataEnd) {
                /*
                 * 流當前在一個字段值塊通過默認序列化寫入的末尾。因爲沒有終點TC_ENDBLOCKDATA標誌,
                 * 明確地模仿常規數據結束行爲。
                 */
                return -1;
            }
            try {
                for (;;) {
                    int avail = canBlock ? Integer.MAX_VALUE : in.available();//可以阻塞時avail是最大整數,不能阻塞時是流中能讀取的字節數
                    if (avail == 0) {
                        return HEADER_BLOCKED;//沒有可讀取的字節數時返回HEADER_BLOCKED
                    }

                    int tc = in.peek();//從流中取數
                    switch (tc) {
                        case TC_BLOCKDATA://第二個字節是塊數據長度
                            if (avail < 2) {
                                return HEADER_BLOCKED;
                            }
                            in.readFully(hbuf, 0, 2);
                            return hbuf[1] & 0xFF;

                        case TC_BLOCKDATALONG://塊字節長度需要4個字節來表示
                            if (avail < 5) {
                                return HEADER_BLOCKED;
                            }
                            in.readFully(hbuf, 0, 5);
                            int len = Bits.getInt(hbuf, 1);//位運算獲取後4位的整數值
                            if (len < 0) {
                                throw new StreamCorruptedException(
                                    "illegal block data header length: " +
                                    len);
                            }
                            return len;

                        /*
                         * TC_RESET可能發生在數據塊之間。不幸的是,這個狀況必須在比其他類型標誌更低的級別被解析,
                         * 因爲原始數據讀取可能跨越被TC_RESET分割的數據塊
                         */
                        case TC_RESET:
                            in.read();//跳過這個頭部
                            handleReset();//重置
                            break;

                        default:
                            if (tc >= 0 && (tc < TC_BASE || tc > TC_MAX)) {
                                throw new StreamCorruptedException(
                                    String.format("invalid type code: %02X",
                                    tc));
                            }
                            return -1;
                    }
                }
            } catch (EOFException ex) {
                throw new StreamCorruptedException(
                    "unexpected EOF while reading block data header");
            }
        }

單個字節的read和peek都是基於refill來完成的,而read多字節時,最大不能超過緩衝區內剩餘的字節,如果copy爲true,需要先將字節讀取到一個內部緩衝區再複製到目標數組,又增加開銷。

        int read(byte[] b, int off, int len, boolean copy) throws IOException {
            if (len == 0) {
                return 0;
            } else if (blkmode) {
                if (pos == end) {
                    refill();
                }
                if (end < 0) {
                    return -1;
                }
                int nread = Math.min(len, end - pos);//最大不超過緩衝區內剩餘的字節
                System.arraycopy(buf, pos, b, off, nread);
                pos += nread;
                return nread;
            } else if (copy) {//copy模式先將數據讀取到一個緩衝區,再從緩衝區複製到目標數組
                int nread = in.read(buf, 0, Math.min(len, MAX_BLOCK_SIZE));
                if (nread > 0) {
                    System.arraycopy(buf, 0, b, off, nread);
                }
                return nread;
            } else {
                return in.read(b, off, len);
            }
        }

readFully循環調用read直到達到要讀取的字節數,如果不足會拋出EOFException

        public void readFully(byte[] b, int off, int len, boolean copy)
            throws IOException
        {
            while (len > 0) {
                int n = read(b, off, len, copy);
                if (n < 0) {
                    throw new EOFException();
                }
                off += n;
                len -= n;
            }
        }

HandleTable

比起輸入流來,輸入流的緩存表多了一個HandleList[] deps用來存儲依賴,HandleList可以理解爲一個簡易版的ArrayList,當數組被填滿再次增加元素的時候,自動分配一個新的2倍大小的數組,把舊的元素複製過去再插入新的。另外兩個數組,Object[] entries存儲對象或異常,byte[] status存儲對象的狀態。並且這個緩存表並沒有使用hash,它是依靠ObjectInputStream中的passHandle來獲取位置的。

        /** array mapping handle -> object status 句柄->對象狀態的數組映射*/
        byte[] status;
        /** array mapping handle -> object/exception (depending on status) */
        Object[] entries;
        /** array mapping handle -> list of dependent handles (if any) */
        HandleList[] deps;
        /** lowest unresolved dependency 最低的未解決依賴*/
        int lowDep = -1;
        /** number of handles in table */
        int size = 0;

從assign我們可以看到,對象和它的狀態(默認爲UNKNOWN)被順序存儲到了數組中,並返回下標值

        int assign(Object obj) {
            if (size >= entries.length) {
                grow();
            }
            status[size] = STATUS_UNKNOWN;
            entries[size] = obj;
            return size++;
        }

關於deps這個數組到底是幹什麼的,我們可以一步步來分析。首先,尋找deps在HandleTable中被修改的地方,除了構造時初始化以外,finish中deps對應狀態爲UNKNOWN狀態的全部被設爲NULL,clear當中deps全部爲設爲null,grow當中新建了一個大小是原本2倍加1的deps數組並複製了原有內容來取代之前的數組,這些都不足以分析它的用途,關鍵在markDependency和markException兩個方法中。首先markException我們可以看到所有調用都是發生在出現ClassNotFoundException的時候,傳入的參數是這個類的handle值和異常。markDependency關聯一個ClassNotFoundException和當前活動句柄並傳播它到其他合適的引用對象。這個特定的句柄必須是打開的。簡單的來說,如果一個類解析失敗,那麼添加到entries緩存裏的對象會是一個異常,它對應的status爲STATUS_EXCEPTION

        void markException(int handle, ClassNotFoundException ex) {
            switch (status[handle]) {
                case STATUS_UNKNOWN:
                    status[handle] = STATUS_EXCEPTION;//標記當前對象狀態爲異常
                    entries[handle] = ex;//修改緩存中的對象爲異常

                    // propagate exception to dependents傳播異常給依賴
                    HandleList dlist = deps[handle];
                    if (dlist != null) {
                        int ndeps = dlist.size();
                        for (int i = 0; i < ndeps; i++) {
                            markException(dlist.get(i), ex);//遞歸傳播依賴列表內的所有對象
                        }
                        deps[handle] = null;//清除依賴表中的對象,因爲只要再有對象關聯到相同類它必然是會從緩存中獲取異常,不再需要依賴表項
                    }
                    break;

                case STATUS_EXCEPTION:
                    break;

                default:
                    throw new InternalError();
            }
        }

再來看一下markDependency的調用,一處是在readObject調用readObject0之後,傳入參數是上一層對象的句柄和本層對象的句柄;一處是在readArray中讀取非基本數據類型調用readObject0之後,傳入參數也是上一層對象的句柄和本層對象的句柄;一處是在defaultReadFields中遞歸讀取非基本數據類中,傳入參數也是上一層對象的句柄和本層對象的句柄。總之,根據上面這幾個例子再結合方法的註釋,我們可以推測出這裏deps的作用是在一層層讀取對象時,比如自定義類中又又自定義類,記錄下一層指向上一層的關係,用來傳遞異常狀態。很明顯,從代碼中可以看出,在存在依賴雙方的情況下,如果上一層狀態是UNKNOWN,下一層狀態是EXCEPTION,則從依次向上將整個依賴表上所有的狀態全部置爲EXCEPTION,如果下一層也是UNKNOWN狀態,則在下一層的依賴表中增加上一層的句柄,並將未知狀態最底層設爲target。如果上一層狀態是OK,則出現InternalError,因爲上一層的句柄已經被關閉,不能再增加對它依賴的對象。

        void markDependency(int dependent, int target) {
            if (dependent == NULL_HANDLE || target == NULL_HANDLE) {
                return;
            }
            switch (status[dependent]) {

                case STATUS_UNKNOWN:
                    switch (status[target]) {
                        case STATUS_OK:
                            // ignore dependencies on objs with no exception忽略沒有異常的對象上依賴
                            break;

                        case STATUS_EXCEPTION:
                            // eagerly propagate exception急切傳播的異常
                            markException(dependent,
                                (ClassNotFoundException) entries[target]);//如果依賴是未知狀態也需將它們改爲異常狀態
                            break;

                        case STATUS_UNKNOWN:
                            // add to dependency list of target增加到依賴目標列表
                            if (deps[target] == null) {
                                deps[target] = new HandleList();
                            }
                            deps[target].add(dependent);

                            // remember lowest unresolved target seen記錄被看見最低的未解決目標
                            if (lowDep < 0 || lowDep > target) {
                                lowDep = target;
                            }
                            break;

                        default:
                            throw new InternalError();
                    }
                    break;

                case STATUS_EXCEPTION:
                    break;

                default:
                    throw new InternalError();
            }
        }

finish是關閉句柄,是唯一可以將狀態設爲STATUS_OK的方法,標記給出的句柄爲結束狀態,說明這個句柄不會有新的依賴標記。調用設置和結束方法必須以後進先出的順序。必須要handle之前不存在無法確認的狀態才能修改狀態爲OK

        void finish(int handle) {
            int end;
            if (lowDep < 0) {
                // 沒有還沒處理的未知狀態,只需要解決當前句柄
                end = handle + 1;
            } else if (lowDep >= handle) {
                // handle之後存在還沒有處理的未知狀態,但上層句柄都已經解決了
                end = size;
                lowDep = -1;
            } else {
                // handle之前還有沒有處理的向前引用,還不能解決所有對象
                return;
            }

            // change STATUS_UNKNOWN -> STATUS_OK in selected span of handles
            for (int i = handle; i < end; i++) {
                switch (status[i]) {
                    case STATUS_UNKNOWN:
                        status[i] = STATUS_OK;
                        deps[i] = null;
                        break;

                    case STATUS_OK:
                    case STATUS_EXCEPTION:
                        break;

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