在閱讀ArrayList源碼時(shí)帖渠,發(fā)現(xiàn)保存元素的數(shù)組 elementData 使用 transient 修飾谒亦,該關(guān)鍵字聲明數(shù)組默認(rèn)不會(huì)被序列化。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
// Android-note: Also accessed from java.util.Collections
transient Object[] elementData; // non-private to simplify nested class access
那么在序列化后空郊,ArrayList里面的元素?cái)?shù)組保存的數(shù)據(jù)不就完全丟失了嗎份招?
深入研究代碼后發(fā)現(xiàn)事實(shí)上,并不會(huì)狞甚,ArrayList提供了兩個(gè)用于序列化和反序列化的方法锁摔,
readObject和writeObject:
/**
* 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 size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// 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 {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
ArrayList在序列化的時(shí)候會(huì)調(diào)用writeObject,直接將size和element寫入ObjectOutputStream哼审;
反序列化時(shí)調(diào)用readObject谐腰,從ObjectInputStream獲取size和element,再恢復(fù)到elementData涩盾。
為什么不直接用elementData來(lái)序列化十气,而采用上面的方式來(lái)實(shí)現(xiàn)序列化呢?
原因在于elementData是一個(gè)緩存數(shù)組旁赊,默認(rèn)size為10,對(duì)ArrayList進(jìn)行add操作當(dāng)空間不足時(shí)桦踊,
會(huì)對(duì)ArrayList進(jìn)行擴(kuò)容。通常擴(kuò)容的倍數(shù)為1.5倍终畅。
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
所以elementData數(shù)組會(huì)預(yù)留一些容量籍胯,等容量不足時(shí)再擴(kuò)充容量,那么有些空間可能就沒(méi)有實(shí)際存儲(chǔ)元素离福,采用上面的方式來(lái)實(shí)現(xiàn)序列化時(shí)杖狼,就可以保證只序列化實(shí)際存儲(chǔ)的那些元素,而不是整個(gè)數(shù)組妖爷,從而節(jié)省空間和時(shí)間蝶涩。
序列化的時(shí)候是怎么調(diào)用writeObject和readObject的
奇怪了?盡管writeObject和readObject被外部類調(diào)用但事實(shí)上這是兩個(gè)private的方法絮识。并且它們既不存在于java.lang.Object绿聘,也沒(méi)有在Serializable中聲明。那么ObjectOutputStream如何使用它們的呢次舌?
序列化時(shí)需要使用 ObjectOutputStream 的 writeObject() 將對(duì)象轉(zhuǎn)換為字節(jié)流并輸出熄攘。而 writeObject() 方法在傳入的對(duì)象存在 writeObject() 的時(shí)候會(huì)去反射調(diào)用該對(duì)象的 writeObject() 來(lái)實(shí)現(xiàn)序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法彼念,原理類似挪圾。
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);
我們以O(shè)bjectInputStream為例浅萧,大體梳理一下調(diào)用流程,感興趣的同學(xué)可以跟著讀一下源碼
首先哲思,反序列化時(shí)會(huì)調(diào)用readObject() -> Object obj = readObject0(false) -> readObject0 -> return checkResolve(readOrdinaryObject(unshared)) -> readOrdinaryObject -> readSerialData(obj, desc);
然后readSerialData會(huì)調(diào)用slotDesc.invokeReadObject(obj, this)
這里調(diào)用ObjectStreamClass的invokeReadObject(Object obj, ObjectInputStream in)
里面的readObjectMethod.invoke(obj, new Object[]{ in });
這顯然是一個(gè)通過(guò)反射進(jìn)行的方法調(diào)用洼畅,那么readObjectMethod是什么方法?
readObjectMethod = getPrivateMethod(cl, "readObject",new Class<?>[] { ObjectInputStream.class },Void.TYPE); writeObjectMethod = getPrivateMethod(cl, "writeObject",new Class<?>[] { ObjectOutputStream.class },Void.TYPE);
可以看到writeObjectMethod也在這里
getPrivateMethod方法如下:
/**
* 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;
}
}
到這里我們就大概上明白了,ObjectInputStream會(huì)通過(guò)反射的形式棚赔,調(diào)用private的readObject方法帝簇。
實(shí)現(xiàn)反序列化。
其實(shí)在java集合框架中忆嗜,還有很多中集合都采用了這種方式己儒,修飾數(shù)據(jù)集合數(shù)組,比如
CopyOnWriteArrayList
private transient volatile Object[] elements;
HashMap
transient Node<K,V>[] table;
HashSet
private transient HashMap<E,Object> map;
究其原因捆毫,都是為了保證只序列化實(shí)際存儲(chǔ)的那些元素闪湾,而不是整個(gè)數(shù)組,從而節(jié)省空間和時(shí)間绩卤。
參考文章:
JAVA對(duì)象流序列化時(shí)的readObject途样,writeObject,readResolve是怎么被調(diào)用的