欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JAVA对象流序列化时的readObject,writeObject,readResolve是怎么被调用的

程序员文章站 2024-03-04 10:46:53
...

有时候,我们会在很多涉及到通过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的源码,你将会发现

JAVA对象流序列化时的readObject,writeObject,readResolve是怎么被调用的


对的,整个用于保存信息的map(HashSet和LinkedHashSet底层就是用HashMap实现的,CopyOnWriteArraySet底层是用CopyOnWriteArrayList实现的,等等的各种set实现类源码有兴趣可以阅读一下源码)是被用transient关键字修饰了的。


但是,很明显我们的信息是有被序列化成功的,不然反序列化出来时,原本保存在set里面的信息就丢失了。真正的实现的秘密就在于上面提到的readObject方法与writeObject方法了。这里仅简单介绍一下readObject方法,writeObject方法与其类似,不做介绍了。


由于篇幅限制,这里不贴出readObject的源码并一一分析了,它主要做的事情,其实就是读取正常应该被序列化的字段信息后,再构造出一个map,再通过对象流,将原有通过对象流写进文件里面的map信息(容量,每个item信息等)全部读取出来,然后重新构造一个map,这样就使得我们保存在set里面的信息,在经历过对象流的序列化和反序列化后,都没有丢失。那么,这个是private 的 readObject方法是怎么被调用的呢?


简易调用流程图(具体信息,还是需要自己跟踪一下源码了):


JAVA对象流序列化时的readObject,writeObject,readResolve是怎么被调用的


看到这里,我们算是大概明白了,为什么诸如HashSet的类里面,要写private 的readObject方法了,因为对象流的读取过程中,它会通过反射的形式,调用private的readObject方法。当然,整个流程走到这里,还是没有走完的,下面还有很多步骤要做,但是因为不属于本篇的讨论范围,这里就不述说了。

但是,我们还有一个疑惑,那就是ObjectStreamClass是什么时候产生的呢?而readObjectMethod这个属性又是怎么得到的呢。

答案就是在ObjectInputStream类的readOrdinaryObject方法调用中:


JAVA对象流序列化时的readObject,writeObject,readResolve是怎么被调用的


最后,我们也知道源码里面,是在哪里获得了,我们单例时,所写的那个private 的 readResolve方法的引用了。那么又在整个流程中哪个步骤,通过那个引用,实际调用了我们的readResolve方法呢?答案就是在一开始很前面的ObjectInputStream类的readOrdinaryObject方法,在调用完readSerialData()方法后,就调用了 ObjectStreamClass类的Object invokeReadResolve(Object obj)方法,通过反射调用了我们自己写的readResolve方法,这里不再展开述说了。


本文到此也是要结束了,第一次写源码分析的文章,已经尽力在保持篇幅的情况下,尽量阐述清楚我想要阐述的内容了,也说明白了神奇的三个private 方法:readObject,writeObject,readResolve是在哪里被调用的。

最后,有什么不足之处,望能指出,谢谢。