入口:
transient Object[] elementData; // non-private to simplify nested class access---ArrayList源码片段
transient
是很容易被忽略的关键字。大家都知道,此修饰符是为了让被修饰的成员变量不被序列化,可以一定程度上节约网络开销或内存。上图中 elementData
作为存储元素的数组,被修饰为 transient
,看似不合理,实际是因为ArrayList
中实现了writeObject(ObjectOutputStream out)
和readObject(ObjectInputStream in)
方法,以writeObject
说明,如下:
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();
}
}
此处,又有个疑问,序列化时(通过ObjectOutputStream
进行),writeObject
方法(反序列同样)是如何被调用的?除了反射想不到其他方法,跟踪源码发现果然是采用了反射机制。
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
...
slotDesc.invokeWriteObject(obj, this);//line 1460
...
}
总结下序列化的相关知识点:
1、static
和transient
声明的变量和对象方法都不参与序列化;
2、自定义序列化有两种方式:继承Serializable
或者Externalizable
接口,事实上Externalizable
是Serializable
的子接口。个人建议采用继承Serializable
,实现writeObject
和readObject
方法的方式,可以通过s.defaultWriteObject()
默认序列化,在此基础上你可以按需实现,ArrayList就是如此。
3、最好为每个序列化的类定义serialVersionUID
,像ArrayList的源码一样:
private static final long serialVersionUID = 8683452581122892189L;
这样可以保证序列版本的一致性。
4、如果序列化对象引用了其他对象,其他对象也会被序列化,如果被引用对象未实现接口,则会抛出异常。正因为此特性,序列化也可以被用来进行深拷贝。