欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于map和null的一些小故事

程序员文章站 2022-07-05 11:46:32
...
转载自 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”.


相关标签: map与mull