初探Java序列化(2)-writeObject/readObject,javawriteobject

來源:互聯網
上載者:User

初探Java序列化(2)-writeObject/readObject,javawriteobject

        上一篇《初探Java序列化(Serialization)》給我們大體介紹了什麼是序列化和還原序列化,以及解析了一下序列化出來的檔案。接著我們看看JDK具體如何序列化一個Object。

        在序列化過程中,虛擬機器會試圖調用對象類裡的writeObject() 和readObject(),進行使用者自訂的序列化和還原序列化,如果沒有則調用ObjectOutputStream.defaultWriteObject() 和ObjectInputStream.defaultReadObject()。同樣,在ObjectOutputStream和ObjectInputStream中最重要的方法也是writeObject() 和 readObject(),遞迴地寫出/讀入byte。

        所以使用者可以通過writeObject()和 readObject()自訂序列化和還原序列化邏輯。對一些敏感資訊加密的邏輯也可以放在此。【不過此處不會檢查serialVersionUID】

        對於一個Obj來說,都是先寫類資訊description,再寫屬性field。

        下面是defaultWriteObject()和defaultReadObject(),詳見JDK1.8的ObjectOutputStream和ObjectInputStream。

    public void defaultWriteObject() throws IOException {        SerialCallbackContext ctx = curContext;        if (ctx == null) {            throw new NotActiveException("not in call to writeObject");        }        Object curObj = ctx.getObj();        ObjectStreamClass curDesc = ctx.getDesc();        bout.setBlockDataMode(false);        defaultWriteFields(curObj, curDesc);        bout.setBlockDataMode(true);    }    private void defaultWriteFields(Object obj, ObjectStreamClass desc) {        Class<?> cl = desc.forClass();        if (cl != null && obj != null && !cl.isInstance(obj))             throw new ClassCastException();        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() + "\")");            writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());    }
        從上面的代碼能看出來,JAVA在序列化write的過程中,根據field的類型分成 基本類型 和 對象。

解析基本類型 getPrimFieldValues()

        JAVA會把對象中field的相應記憶體位址記錄起來,拼裝在FieldReflector對象中,然後通過unsafe來讀取其中的基本類型的值,並將其轉換成最終要寫出的byte[]。

        【上述操作在ObjectStreamClass中完成】

        fieldRefl = getReflector(fields, this);FieldReflector(ObjectStreamField[] fields) {            this.fields = fields;            int nfields = fields.length;            readKeys = new long[nfields];            writeKeys = new long[nfields];            offsets = new int[nfields];            typeCodes = new char[nfields];            ArrayList<Class<?>> typeList = new ArrayList<>();            Set<Long> usedKeys = new HashSet<>();            for (int i = 0; i < nfields; i++) {                ObjectStreamField f = fields[i];                Field rf = f.getField();                long key = (rf != null) ?                    unsafe.objectFieldOffset(rf) : Unsafe.INVALID_FIELD_OFFSET;                readKeys[i] = key;                writeKeys[i] = usedKeys.add(key) ? key : Unsafe.INVALID_FIELD_OFFSET;                offsets[i] = f.getOffset();                typeCodes[i] = f.getTypeCode();                if (!f.isPrimitive())                     typeList.add((rf != null) ? rf.getType() : null);            }            types = typeList.toArray(new Class<?>[typeList.size()]);            numPrimFields = nfields - types.length;        }void getPrimFieldValues(Object obj, byte[] buf) {            if (obj == null) throw new NullPointerException();            for (int i = 0; i < numPrimFields; i++) {                long key = readKeys[i];                int off = offsets[i];                switch (typeCodes[i]) {                    case 'Z':                        Bits.putBoolean(buf, off, unsafe.getBoolean(obj, key));                        break;                    case 'B':                        buf[off] = unsafe.getByte(obj, key);                        break;                    case 'C':                        Bits.putChar(buf, off, unsafe.getChar(obj, key));                        break;                    case 'S':                        Bits.putShort(buf, off, unsafe.getShort(obj, key));                        break;                    case 'I':                        Bits.putInt(buf, off, unsafe.getInt(obj, key));                        break;                    case 'F':                        Bits.putFloat(buf, off, unsafe.getFloat(obj, key));                        break;                    case 'J':                        Bits.putLong(buf, off, unsafe.getLong(obj, key));                        break;                    case 'D':                        Bits.putDouble(buf, off, unsafe.getDouble(obj, key));                        break;                    default: throw new InternalError();                }            }        }

        【基本類型和byte的相互轉換,都是在Bits類中處理的,如果你對其感興趣,可以好好研究一下JAVA中的基本類型和byte的轉換

        另外對於float和double都是先轉換成long,再轉成byte。

public static native int floatToRawIntBits(floatvalue);

public static native long doubleToRawLongBits(doublevalue);


解析物件類型 getObjFieldValues()

        寫對象比基本類型要複雜,JDK先要查看Class是不是已經有序列化的記錄,可以直接複用ClassDesc;另外需要判斷對象的類型,Null,Handle,Class,Array,String,Enum等。最後在writeOrdinaryObject()中又會調用writeSerialData()和defaultWriteFields()來遞迴寫入基本類型。

        詳細可以參考ObjectOutputStream.writeObject0()如下。
    private void writeObject0(Object obj, boolean unshared) {        boolean oldMode = bout.setBlockDataMode(false);        depth++;        try {            // check for replacement object            Object orig = obj;            Class<?> cl = obj.getClass();            ObjectStreamClass desc;            for (;;) {                Class<?> repCl;                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) {                writeOrdinaryObject(obj, desc, unshared);            } else                 throw new NotSerializableException(cl.getName());        } finally {            depth--;            bout.setBlockDataMode(oldMode);        }    }


        另外,我看了一下典型的String序列化策略。第一個字元作為String的類型標識,有普通String和長String,然後接著標記長度,最後才是用UTF寫內容。

    private void writeString(String str, boolean unshared) throws IOException {        handles.assign(unshared ? null : str);        long utflen = bout.getUTFLength(str);        if (utflen <= 0xFFFF) {            bout.writeByte(TC_STRING);            bout.writeUTF(str, utflen);        } else {            bout.writeByte(TC_LONGSTRING);            bout.writeLongUTF(str, utflen);        }    }    void writeUTF(String s, long utflen) throws IOException {        if (utflen > 0xFFFFL)             throw new UTFDataFormatException();        writeShort((int) utflen);        writeBytes(s);    }


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.