寫在開頭
今天在寫《HashMap很美好,但線程不安全怎么辦?ConcurrentHashMap告訴你答案!》這篇文章的時候,漏了一個知識點,知道晚上吃飯的時候才凸顯想到,關于ConcurrentHashMap在存儲Key與Value的時候,是否可以存null的問題,按理說這是一個小問題,但build哥卻不敢忽視,尤其在現在很多面試官都極具挑剔的環境下,萬一同學們刷到了咱的博客,回答中遺漏了這個小細節,錯過了面試官的考驗,那咱可就成罪人了。
接下來我們就將HashMap、Hashtable、ConcurrentHashMap這三集合類的鍵值是否可以null的問題,放一起對比去學習一下。
Hashtable的鍵值與null
雖然我們在講解HashMap與Hashtable作對比時,已經說了Hashtable在存儲key與value時均不可為null,但當時的側重點全在HashMap身上,就沒有詳細的解釋原因,下面我們跟進put源碼中去一探緣由。
【源碼解析1】
public synchronized V put(K key, V value) {// 確認值不為空if (value == null) {throw new NullPointerException(); // 如果值為null,則拋出空指針異常}// 確認值之前不存在Hashtable里Entry<?,?> tab[] = table;int hash = key.hashCode(); // 如果key如果為null,調用這個方法會拋出空指針異常int index = (hash & 0x7FFFFFFF) % tab.length;//計算存儲位置//遍歷,看是否鍵或值對是否已經存在,如果已經存在返回舊值@SuppressWarnings("unchecked")Entry<K,V> entry = (Entry<K,V>)tab[index];for(; entry != null ; entry = entry.next) {if ((entry.hash == hash) && entry.key.equals(key)) {V old = entry.value;entry.value = value;return old;}}addEntry(hash, key, value, index);return null;}
通過Hashtable的put底層源碼,我們可以看到,方法體內,首先就對value值進行的判空操作,如果為空則拋出空指針異常;其次在計算hash值的時候,直接調用key的hashCode()方法,若keynull,自然也會報空指針異常,因此,我們在調用put方法存儲鍵值對時,key與value都非null。
HashMap的鍵值與null
我們同樣也通過HashMap的put方法去分析它的底層源碼,先上代碼。
【源碼解析2-hash()】
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
在計算hash值的時候,hashmap中通過三目運算符做了空值處理,直接返回0,這樣最終計算出key應該存儲在數組的第一位上,且key是唯一性呢,因此,key最多存一個null;
【源碼解析3】
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {// 數組HashMap.Node<K,V>[] tab; // 元素HashMap.Node<K,V> p; // n 為數組的長度 i 為下標int n, i;// 數組為空的時候if ((tab = table) == null || (n = tab.length) == 0)// 第一次擴容后的數組長度n = (tab = resize()).length;// 計算節點的插入位置,如果該位置為空,則新建一個節點插入if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);///
}
回歸putVal()方法,我們逐句閱讀后也沒有發現對于value值為null的處理與限定,因此,它可以存儲為null的value值,我們知道HashMap的鍵值對特點如同身份證與人名一樣,key等同于身份證,全國唯一,而value值等同于人名,可以重復,比如全國有上萬個叫張偉的,所以value值也就同樣允許存儲多個null。
ConcurrentHashMap的鍵值與null
很多同學們可能會以為ConcurrentHashMap不過是HashMap在多線程環境下的版本,底層實現都一致,只是多了加鎖的操作,所以二者對于null的允許程度是一樣。
如果你是這樣想,那可就完全錯了,對于ConcurrentHashMap來說,它也不允許存儲鍵值對為null的數據。
Doug Lea(ConcurrentHashMap的設計者)曾這樣說道:
The main reason that nulls aren’t allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can’t be accommodated. The main one is that if map.get(key) returns null, you can’t detect whether the key explicitly maps to null vs the key isn’t mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.
大致的意思是,在單線程環境中,不會存在一個線程操作該 HashMap 時,其他的線程將該 HashMap 修改的情況,可以通過 contains(key)來做判斷是否存在這個鍵值對,從而做相應的處理;
而在多線程環境下,可能會存在多個線程同時修改鍵值對的情況,這時是無法通過contains(key)來判斷鍵值對是否存在的,這會帶來一個二義性的問題,Doug Lea說二義性是多線程中不能容忍的!
啥是二義性? 咱們通俗點講就是一個結果,2種釋義,就好比我們通過get方法獲取值的時候,返回一個null,其實我們是無法判斷是值本身為null還是說集合中就沒這個值!
所以說,ConcurrentHashMap的key和value均不可為null。
結尾彩蛋
如果本篇博客對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯系Build哥!
如果您想與Build哥的關系更近一步,還可以關注俺滴公眾號“JavaBuild888”,在這里除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!