原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。java
若是你有仔細閱讀過HashMap的源碼,那麼你必定注意過一個問題:HashMap中有兩個私有方法。程序員
private void writeObject(java.io.ObjectOutputStream s) throws IOException
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
複製代碼
這兩個方法有兩個共同點:數組
這兩個方法是幹嗎用的?bash
爲何要設置成私有的?微信
答:readObject和writeObject方法都是爲了HashMap的序列化而建立的。架構
首先,HashMap實現了Serializable接口,這意味着該類能夠被序列化,而JDK提供的對於Java對象序列化操做的類是ObjectOutputStream,反序列化的類是ObjectInputStream。咱們來看下序列化使用的ObjectOutputStream,它提供了不一樣的方法用來序列化不一樣類型的對象,好比writeBoolean,wrietInt,writeLong等,對於自定義類型,提供了writeObject方法。 ObjectOutputStream的writeObject方法會調用下面的方法:併發
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {//若是重寫了writeObject方法
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this); //調用實現類本身的writeobject方法
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
//省略
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
複製代碼
或者下面這張圖也能夠:ide
能夠看到,實際上在ObjectOutputStream中進行序列化操做的時候,會判斷被序列化的對象是否本身重寫了writeObject方法,若是重寫了,就會調用被序列化對象本身的writeObject方法,若是沒有重寫,纔會調用默認的序列化方法。高併發
調用關係以下圖:ui
JDK文檔中並無明確說明設置爲私有的緣由。方法是私有的,那麼該方法沒法被子類override,這樣作有什麼好處呢? 若是我實現了一個繼承HashMap的類,我也想有本身的序列化和反序列化方法,那我也能夠實現私有的readObject和writeObject方法,而不用關心HashMap本身的那一部分。 下面的部分來自StackOverFlow:
We don’t want these methods to be overridden by subclasses. Instead, each class can have its own writeObject method, and the serialization engine will call all of them one after the other. This is only possible with private methods (these are not overridden). (The same is valid for readObject.)
首先要明確序列化的目的,將java對象序列化,必定是爲了在某個時刻可以將該對象反序列化,並且通常來說序列化和反序列化所在的機器是不一樣的,由於序列化最經常使用的場景就是跨機器的調用,而序列化和反序列化的一個最基本的要求就是,反序列化以後的對象與序列化以前的對象是一致的。
HashMap中,因爲Entry的存放位置是根據Key的Hash值來計算,而後存放到數組中的,對於同一個Key,在不一樣的JVM實現中計算得出的Hash值多是不一樣的。
Hash值不一樣致使的結果就是:有可能一個HashMap對象的反序列化結果與序列化以前的結果不一致。即有可能序列化以前,Key=’AAA’的元素放在數組的第0個位置,而反序列化值後,根據Key獲取元素的時候,可能須要從數組爲2的位置來獲取,而此時獲取到的數據與序列化以前確定是不一樣的。
在《Effective Java》中,Joshua大神對此有所解釋:
For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. The bucket that an entry resides in is a function of the hash code of its key, which is not, in general, guaranteed to be the same from JVM implementation to JVM implementation. In fact, it isn’t even guaranteed to be the same from run to run. Therefore, accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt.
因此爲了不這個問題,HashMap採用了下面的方式來解決:
那麼,HashMap又是經過什麼手段來保證序列化和反序列化數據的一致性的呢? 首先,HashMap序列化的時候不會將保存數據的數組序列化,而是將元素個數以及每一個元素的Key和Value都進行序列化。 在反序列化的時候,從新計算Key和Value的位置,從新填充一個數組。 想一想看,是否是可以解決序列化和反序列化不一致的狀況呢? 因爲不序列化存放元素的Entry數組,而是反序列化的時候從新生成,這樣就避免了反序列化以後根據Key獲取到的元素與序列化以前獲取到的元素不一樣。
做者簡介:小姐姐味道 (xjjdog),一個不容許程序員走彎路的公衆號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高併發世界,給你不同的味道。個人我的微信xjjdog0,歡迎添加好友,進一步交流。