关于map和null的一些小故事

转载自  https://blog.csdn.net/u010666119/article/details/53873876


因为项目里一处ConcurrentHashMap put value是 null时报错,当时我是震惊的,和hashmap不一样吗?不一样吗?不一样吗?   额 还真不一样。 


前几天看谷歌的Guava对HashMap#get(Object key)方法进行了一些解释,如果返回null,可分为两种情形,
1.当前key下,所对应的value = null
2.当前key不存在,返回null
这确实是令人有些疑惑,当然针对这些情形,可以使用HashMap#containsKey(Object key)进行判断。

记得之前有看过在Java中对map的实现中对于key value为null的情况有不同的实现有不同的处理,常常在一起比较的是Hashtable和HashMap
这几天翻了源码,看看内部如何进行处理,加深理解。
重点比较了put和get操作,其他操作的判断逻辑也应该相通。展示put和get操作。
1.put
1.hashtable, K,V均不能为null,代码显示的对value进行null判断,但注意下边有key.hashCode(),如果key为null,会发生什么呢。
[java] view plain copy
  1. public synchronized V put(K key, V value) {  
  2.     // Make sure the value is not null  
  3.     if (value == null) {  
  4.         throw new NullPointerException();  
  5.     }  
  6.   
  7.     // Makes sure the key is not already in the hashtable.  
  8.     Entry<?,?> tab[] = table;  
  9.     //key 不能为null  
  10.     int hash = key.hashCode();  
  11.     .....  
  12.     }  
2.HashMap K,V可为null, null 的hash返回0,所以多次Key为null会覆盖Value, 可以有多个不同的Key的Value为null。
注意hash()方法,hash()方法对Key是否为null进行判断,在null时hashCode = 0,不为null是key#hashCode().
[java] view plain copy
  1. public V put(K key, V value) {  
  2.         return putVal(hash(key), key, value, falsetrue);  
  3.     }  
[java] view plain copy
  1. static final int hash(Object key) {  
  2.         int h;  
  3.         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  
  4.     }  
[java] view plain copy
  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,  
  2.                boolean evict) {  
  3.     Node<K,V>[] tab; Node<K,V> p; int n, i;  
  4.     if ((tab = table) == null || (n = tab.length) == 0)  
  5.         n = (tab = resize()).length;  
  6.     if ((p = tab[i = (n - 1) & hash]) == null)  
  7.         tab[i] = newNode(hash, key, value, null);  
  8.     else {  
  9.         Node<K,V> e; K k;  
  10.         if (p.hash == hash &&  
  11.             ((k = p.key) == key || (key != null && key.equals(k))))  
  12.             e = p;  
  13.         else if (p instanceof TreeNode)  
  14.             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  
  15.         else {  
  16.             for (int binCount = 0; ; ++binCount) {  
  17.                 if ((e = p.next) == null) {  
  18.                     p.next = newNode(hash, key, value, null);  
  19.                     if (binCount >= TREEIFY_THRESHOLD - 1// -1 for 1st  
  20.                         treeifyBin(tab, hash);  
  21.                     break;  
  22.                 }  
  23.                 if (e.hash == hash &&  
  24.                     ((k = e.key) == key || (key != null && key.equals(k))))  
  25.                     break;  
  26.                 p = e;  
  27.             }  
  28.         }  
  29.         if (e != null) { // existing mapping for key  
  30.             V oldValue = e.value;  
  31.             if (!onlyIfAbsent || oldValue == null)  
  32.                 e.value = value;  
  33.             afterNodeAccess(e);  
  34.             return oldValue;  
  35.         }  
  36.     }  
  37.     ++modCount;  
  38.     if (++size > threshold)  
  39.         resize();  
  40.     afterNodeInsertion(evict);  
  41.     return null;  
  42. }  
3.ConcurrentHashMap, K,V均不能为null, ConcurrentHashMap中通过显示的null判断,对Key和Value均进行了验证
[java] view plain copy
  1. public V put(K key, V value) {  
  2.         return putVal(key, value, false);  
  3.     }  
[java] view plain copy
  1. final V putVal(K key, V value, boolean onlyIfAbsent) {  
  2.         if (key == null || value == nullthrow new     NullPointerException();  
  3.     int hash = spread(key.hashCode());  
  4.     //....}  
2.get
1.Hashtable, K 不可为 null
[java] view plain copy
  1. public synchronized V get(Object key) {  
  2.         Entry<?,?> tab[] = table;  
  3.         int hash = key.hashCode();  
  4.         //....  
  5.     }  
2.HashMap,K可以为null
[java] view plain copy
  1. public V get(Object key) {  
  2.         Node<K,V> e;  
  3.         return (e = getNode(hash(key), key)) == null ? null : e.value;  
  4.     }  
3.ConcurrentHashMap, K不能为null
[java] view plain copy
  1. public V get(Object key) {  
  2.         Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;  
  3.         int h = spread(key.hashCode());  
  4.     //......  
  5. }  
最后,重点,为什么同样的key-value结构,hashmap就能putnull,啊?蛤?

找到了这样的解答: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.

理解:ConcurrentHashmap和Hashtable都是支持并发的,这样会有一个问题,当你通过get(k)获取对应的value时,如果获取到的是null时,你无法判断,它是put(k,v)的时候value为null,还是这个key从来没有做过映射。HashMap是非并发的,可以通过contains(key)来做这个判断。而支持并发的Map在调用m.contains(key)和m.get(key),m可能已经不同了。

个人觉得这个解答还是很有道理的,也是解决了心头的一个疑惑,大牛们在设计时确实考虑的很多,在这里分享给大家。

类似的解答还有这个: 
down vote 
I believe it is, at least in part, to allow you to combine containsKey and get into a single call. If the map can hold nulls, there is no way to tell if get is returning a null because there was no key for that value, or just because the value was null.

Why is that a problem? Because there is no safe way to do that yourself. Take the following code:

if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}
  • 1
  • 2
  • 3
  • 4
  • 5

Since m is a concurrent map, key k may be deleted between the containsKey and get calls, causing this snippet to return a null that was never in the table, rather than the desired KeyNotPresentException.

Normally you would solve that by synchronizing, but with a concurrent map that of course won’t work. Hence the signature for get had to change, and the only way to do that in a backwards-compatible way was to prevent the user inserting null values in the first place, and continue using that as a placeholder for “key not found”.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值