首先指明:HashMap
可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个。这也是ConcurrentHashMap和HashMap的区别之一。
直接上源码:
HashMap
的#putVal()
代码片段:
ini
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; 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);
else {
ConcurrentHashMap
的#putVal()
代码片段:
ini
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
直观上就能看出ConcurrentHashMap
调用put(key, value)
时,对key,value都进行判空处理,而HashMap
并没有。这样设计的原因直接来看看作者的回答:
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)可以容忍歧义性,所谓的歧义看下面,但是多线程并发(ConcurrentHashMap)不能容忍歧义性,因为并发包的map,调用之间的map可能发生了变化,所以歧义性再加上多线程并发操作是不能容忍的,会加重设计的复杂度和曲折性。
这就是作者这么设计的原因吧
ConcurrentHashMap
的 key 和 value 不能为 null 主要是为了避免模棱两可歧义性 :就拿#get(key)
方法获取value值说说上面提到的歧义性:返回null有两种情况:
① map中没有这个key-value键值对,返回null
② map中有这个key-value键值对,但是value值为null,此时也返回null
这就上面所说的歧义,也就是二义性。