1. 程式人生 > >JAVA物件流序列化時的readObject,writeObject,readResolve是怎麼被呼叫的

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是在哪裡被呼叫的。

最後,有什麼不足之處,望能指出,謝謝。