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以及二义性的深入研究的主要内容,如果未能解决你的问题,请参考以下文章

Hashtable与HashMap有啥区别?

JDK1.8之ConcurrentHashMap

ava集合Set集合之HashTable详解

HashTable和Dictionary的区别

C#中HashTable的用法

HashTable vs HashMap