寫在開頭
昨天在寫《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成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!