HashTable,ConcurrentHashMap,ArrayList,LinkedListHashtMap等值对存储null以及二义性的深入研究
Posted 写Bug的渣渣高
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashTable,ConcurrentHashMap,ArrayList,LinkedListHashtMap等值对存储null以及二义性的深入研究相关的知识,希望对你有一定的参考价值。
本文是自己研究的,如果有任何疑问和补充,请您在评论区指正好嘛,我会立刻修改,以免误导他人.
如果您觉得本文质量不错,可以点个赞和关注一下好嘛.
如果您需要引用本文章的内容,请附加链接,谢谢.
问题描述:
为什么HashTable,ConcurrentHashMap不能保存null键和null值
为什么ArrayList ,LinkedList,甚至是HashMap都允许加入null键和值
博客概述
本文从设计思路到源码进行讲解.下面是概述
HashMap存在二义性 ----> ConcurrentMap,HashTable为什么不存在二义性 ---- > 二义性由 返回值 null ,不能代表元素的存在或者不存在引起 ----> 引申部分 : ArrayList,LinkedList 存在不存在二义性
HashMap能存储null-null,但是有二义性
首先要了解HashMap存在二义性.
HashMap能存储key == null, 也能存储 value == null
二义性是什么先看 get方法,get返回值有两种,一种是 key的值, 还有一种就是 null ,null 有两种含义,不存在该key 以及 存在该 key,但是存的值为 null,这就是二义性
下面来看方法
public V get(Object key)
Node<K,V> e;
// 这里是关键哦,getNode后,会返回Node节点(里面保存了k-v),也就是说不存在的节点,返回的是null,没有异议吧.
// 如果存在呢,也就是找到了一个e ,e != null ,e里面保存了k - v, 也就是返回 e.value 没问题吧,那么假如我value就是null 呢
// 这里就出现了,如果没有这个Node,那么e == null,如果有Node,但是 value == null,返回的也是 null .
//二义性就是,寻找Node的时候,找到了,其value == null,就返回null,找不到也返回null,有没有这个元素,都有可能返回 null,这就是二义性,即 null 代表两种状态,有歧义
return (e = getNode(hash(key), key)) == null ? null : e.value;
这里很简单,就是找到了返回元素,没找到返回null
final Node<K,V> getNode(int hash, Object key)
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null)
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null)
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
while ((e = e.next) != null);
return null;
看到上面,你会觉得,二义性就是 通过 get() 方法的返回值,如果返回值是 null 无法判断出是否一定存在该元素.
但是,如果你觉得这是他设计的不好,或者是我讲错了.那你想想,这个"Bug"能解决吗,是否可以通过 contains(key) 方法,也就是说可以通过 contains 来判断是否存在该 key 行不行.
来看看contains方法,
这里可以解决二义性的,因为这里返回的是 boolean ,存在即存在,不存在即 false
public boolean containsKey(Object key)
return getNode(hash(key), key) != null;
public boolean containsValue(Object value)
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0)
for (int i = 0; i < tab.length; ++i)
for (Node<K,V> e = tab[i]; e != null; e = e.next)
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
return false;
有趣的理解:
当 get(key) 的结果为 null 时, 该 key 的存在或者不存在问题类似于薛定谔的猫(如果key存在,这个null就是key的值,如果key不存在,这个 null 只代表了不存在这个key),只有你 containsKey() 之后,才能判断存在这个 key
案例演示:
public class HashMapTest
public static void main(String[] args)
HashMap<String,String> map = new HashMap<>();
System.out.println("put方法,该方法返回null代表的是没有重复的key, 非null是返回的代表被覆盖的值,结果" + map.put("ggzx", null));
System.out.println("get方法返回值,null 可以代表值为 null,也可能代表 不存在,我尝试获取,但结果 " + map.get("ggzx"));
System.out.println(map.containsKey("ggzx"));
System.out.println(map.containsValue(null));
结果:
put方法,该方法返回null代表的是没有重复的key, 非null是返回的代表被覆盖的值,结果null
get方法返回值,null 可以代表值为 null,也可能代表 不存在,我尝试获取,但结果 null
true
true
看一看第二个输出:
如果你知道自己插入了数据,此时null代表了什么
如果你不知道自己插入了什么数据,这个时候你可以断定, null 是值 还是代表不存在.
如果你调用 cotainsKeys() ,能否能判断存在该key吗?
为什么有二义性还要搞
你已经了解了HashMap其实是存在二义性,但是也能够解决,在能解决的基础上,能保存 null-null,感觉也还行对吧.至少能表达一种状态,比如说存储 className --> null ,的情况,null 能代表不存在该 class
为什么HashTable和ConcurrentHashMap不能?
为啥,因为设计者都说了啊,他不想在并发过程中存在这个二义性问题!!!,不过这里我还想冒昧的看看,如果能保存,是否能够用containsKey()来解决
首先来看,Concurrent和HashTable 不能存储 null - null 的证据哇
ConcurrentHashMap#V putVal(K key, V value, boolean onlyIfAbsent)
final V putVal(K key, V value, boolean onlyIfAbsent)
// 对于ConcucrrentHashMap, 如果 key || value == null,都会直接判处
if (key == null || value == null) throw new NullPointerException();
...本文讲null的存储的,又不讲源码,不给你看很多
return null;
public synchronized V put(K key, V value)
// 如果value == null ,直接抛出异常
if (value == null)
throw new NullPointerException();
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
// 如果 key == null,就抛出异常了,不过我也不知道为什么不检测,而需要运行时遇到异常抛出去
int hash = key.hashCode();
... 这部分不给你看了
addEntry(hash, key, value, index);
return null;
好了,目前为止,你已经了解了,他们俩,都不能存储 key == null 或者 value == null.
不能存储 null ,返回值如果为 null ,肯定代表不存在了对吧,此时就没有二义性
看一看ArrayList吧
先看看ArrayList# E get(int index)
这里压根就不存在二义性哇,我传入一个索引,索引只有两个状态,无效的和有效的.结果不存在二义性,如果返回 null 呢?代表的就是存储的 null 呗,没有 null 代表不存在一说
public E get(int index)
// 检查是否越界
rangeCheck(index);
// 直接返回
return elementData(index);
再看看ArrayList#boolean add(E e)
仔细看看返回的是啥,boolean,加入成功就是成功,失败就是失败,也不会返回null.
第二个重载方法,直接 void 了,还有啥二义性可言
// 加入元素
public boolean add(E e)
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
// 重载方法
public void add(int index, E element)
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
下面再看看 LinkedList 吧
存入null之前之后,都是返回的 null ,也无法判断,是否是存在key呢,还是不存在key呢
public class LinkedListTest
public static void main(String[] args)
LinkedList<Integer> list = new LinkedList<>();
System.out.println(list.peekLast());
list.add(null);
list.add(null);
list.add(null);
list.add(null);
System.out.println(list.peekLast());
结果:
null
null
文章结束
最主要的前面的部分,即HashTable,ConcurrentHashMap和HashMap,后面的可以用于拓展吧.
如果你有问题,您可以在评论区进行评论.
你都看到这里了,不打算点个赞和关注嘛
以上是关于HashTable,ConcurrentHashMap,ArrayList,LinkedListHashtMap等值对存储null以及二义性的深入研究的主要内容,如果未能解决你的问题,请参考以下文章