的JavaDoc这样ConcurrentHashMap
说:
类似
Hashtable
但不同HashMap
,这个类就不会允许null
用作键或值。
我的问题:为什么?
第二个问题:为什么Hashtable
不允许null?
我已经使用了很多HashMaps来存储数据。但是当更改为ConcurrentHashMap
NullPointerExceptions 时,我遇到了几次麻烦。
的JavaDoc这样ConcurrentHashMap
说:
类似
Hashtable
但不同HashMap
,这个类就不会允许null
用作键或值。
我的问题:为什么?
第二个问题:为什么Hashtable
不允许null?
我已经使用了很多HashMaps来存储数据。但是当更改为ConcurrentHashMap
NullPointerExceptions 时,我遇到了几次麻烦。
Answers:
从ConcurrentHashMap
他自己的作者(Doug Lea):
ConcurrentMaps(ConcurrentHashMaps,ConcurrentSkipListMaps)不允许使用null的主要原因是,无法容纳在非并行映射中几乎无法容忍的歧义。最主要的是,如果
map.get(key)
returnnull
,则无法检测到该键是否显式映射到null
该键。在非并发映射中,您可以通过进行检查map.contains(key)
,但是在并发映射中,两次调用之间的映射可能已更改。
Optional
内部使用s作为值
Optional
是Java 8的功能,该功能在那时还不可用(Java 5)。Optional
实际上,您现在可以使用s了。
我相信,至少在一定程度上,它可以使您合并containsKey
并get
成为一个通话。如果映射可以容纳null,则get
由于该值没有键,或者仅因为该值为null ,所以无法判断是否返回null。
为什么会有问题呢?因为没有安全的方法自己做。采取以下代码:
if (m.containsKey(k)) {
return m.get(k);
} else {
throw new KeyNotPresentException();
}
由于m
是并发映射,因此可以在containsKey
和get
调用之间删除键k ,从而导致此代码段返回表中从未存在过的null,而不是所需的null KeyNotPresentException
。
通常,您可以通过同步来解决该问题,但是使用并发映射当然是行不通的。因此,get
必须更改for的签名,并且以向后兼容的方式进行签名的唯一方法是防止用户首先插入空值,并继续将其用作“找不到密钥”的占位符。
map.getOrDefault(key, NULL_MARKER)
。如果是null
,则值为null
。如果返回NULL_MARKER
,则不存在该值。
您不能在null上同步。
编辑:这不是在这种情况下的确切原因。最初我以为可以锁定并发更新,或者使用对象监视器来检测是否已修改某些东西,但是在检查源代码时我似乎是错的-他们使用基于哈希的位掩码。
在那种情况下,我怀疑他们是这样做来复制Hashtable的,而我怀疑Hashtable是这样做的,因为在关系数据库世界中,null!= null,因此使用null作为键没有意义。
我猜想API文档的以下片段给出了一个很好的提示:“在依赖于其线程安全性而不依赖于其同步详细信息的程序中,此类可与Hashtable完全互操作。”
他们可能只是想使其与ConcurrentHashMap
完全兼容/可互换Hashtable
。并且因为Hashtable
不允许空键和值。
我认为不允许null值是正确的选择。在许多情况下,我们确实希望将具有空值的键放入并行映射中。但是,通过使用ConcurrentHashMap,我们无法做到这一点。我建议即将发布的JDK版本可以支持该功能。