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