JAVA对象流序列化时的readObject,writeObject,readResolve是怎么被调用的
有时候,我们会在很多涉及到通过JAVA对象流进行序列化和反序列化时,会看到下面的方法:
private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException
以及我们在写我们的单例类时,如果使用的不是枚举的实现形式,为了保证反序列化出来后的对象,不会破坏单例的情况,我们还会经常看到下面的方法;
private Object readResolve()
大家是否会好奇,为什么这些方法都是private的,并且你会发现这些方法都在他们自身的类中,是没有使用的……那么这些方法到底有什么用呢?我们一起跟踪一下源码看看吧。
因为readObject方法与writeObject方法是大同小异的,我这里仅仅阐述一下readObject方法的大概流程,和几个相关的关键点。
首先,我们看一个很简单的程序代码:
public static void main(String[] args) throws Exception {
Set<String> set = new HashSet<String>();
set.add("11111");
set.add("22222");
System.out.println(set);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\lianghaohui\\Desktop\\set.obj"))) {
oos.writeObject(set);
}
set.clear();
System.out.println(set);
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\lianghaohui\\Desktop\\set.obj"))) {
set = (Set<String>) ois.readObject();
}
System.out.println(set);
}
执行结果,也是没什么悬念,将会打印出如下的内容:
[11111, 22222]
[]
[11111, 22222]
但是,如果我们阅读一下HashSet的源码,你将会发现
对的,整个用于保存信息的map(HashSet和LinkedHashSet底层就是用HashMap实现的,CopyOnWriteArraySet底层是用CopyOnWriteArrayList实现的,等等的各种set实现类源码有兴趣可以阅读一下源码)是被用transient关键字修饰了的。
但是,很明显我们的信息是有被序列化成功的,不然反序列化出来时,原本保存在set里面的信息就丢失了。真正的实现的秘密就在于上面提到的readObject方法与writeObject方法了。这里仅简单介绍一下readObject方法,writeObject方法与其类似,不做介绍了。
由于篇幅限制,这里不贴出readObject的源码并一一分析了,它主要做的事情,其实就是读取正常应该被序列化的字段信息后,再构造出一个map,再通过对象流,将原有通过对象流写进文件里面的map信息(容量,每个item信息等)全部读取出来,然后重新构造一个map,这样就使得我们保存在set里面的信息,在经历过对象流的序列化和反序列化后,都没有丢失。那么,这个是private 的 readObject方法是怎么被调用的呢?
简易调用流程图(具体信息,还是需要自己跟踪一下源码了):
看到这里,我们算是大概明白了,为什么诸如HashSet的类里面,要写private 的readObject方法了,因为对象流的读取过程中,它会通过反射的形式,调用private的readObject方法。当然,整个流程走到这里,还是没有走完的,下面还有很多步骤要做,但是因为不属于本篇的讨论范围,这里就不述说了。
但是,我们还有一个疑惑,那就是ObjectStreamClass是什么时候产生的呢?而readObjectMethod这个属性又是怎么得到的呢。
答案就是在ObjectInputStream类的readOrdinaryObject方法调用中:
最后,我们也知道源码里面,是在哪里获得了,我们单例时,所写的那个private 的 readResolve方法的引用了。那么又在整个流程中哪个步骤,通过那个引用,实际调用了我们的readResolve方法呢?答案就是在一开始很前面的ObjectInputStream类的readOrdinaryObject方法,在调用完readSerialData()方法后,就调用了 ObjectStreamClass类的Object invokeReadResolve(Object obj)方法,通过反射调用了我们自己写的readResolve方法,这里不再展开述说了。
本文到此也是要结束了,第一次写源码分析的文章,已经尽力在保持篇幅的情况下,尽量阐述清楚我想要阐述的内容了,也说明白了神奇的三个private 方法:readObject,writeObject,readResolve是在哪里被调用的。
最后,有什么不足之处,望能指出,谢谢。