美文网首页JDK--HashMap源码阅读
JDK1.8 HashMap源码解读 一一put方法

JDK1.8 HashMap源码解读 一一put方法

作者: 从你说谎 | 来源:发表于2018-06-01 11:40 被阅读12次

    建议用 IDEA打开源码配合来看

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    

    实际调用putVal方法:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    

    代码有点长, 我们一点一点来看:

    首先,定义了4个变量 (Node<K,V>[] tab; Node<K,V> p; int n, i;),先不管他是什么意思,然后把一个 HashMap 的一个成员变量赋值给 tab,

    由于我们第一次调用put方法, 所以成员变量此时的值为null,为null会调用一个 resize()方法,我们来看一下这个方法做了些什么。

    我们调用不带参数的构造方法的时候,只对HashMap的负载因子进行了初始化,所以在这里的 threshold 的值为零,然后代码走到了 else 这里,将默认的初始化大小(16)赋值给newCap,然后计算出Map的容量达到多少之后需要扩容。
    这里将扩容的阀值赋值给成员变量 threshold 方便之后存取,然后new一个刚刚计算出数量的Note数组,再将Note数组赋值给成员变量table,此table就是HashMap具体存储数据的地方,接下来,oldTab依然为null,所以直接返回初始化好的Note数组-->> 这里将初始化好的数组的大小赋值给变量n,然后通过 算法(数组的长度 - 1 & key的hash值) 计算出数组的某个下标位置存储的元素赋值给 变量p,再判断这个元素是否为null,如果是null的话,创建一个Note,把note数据存储在计算好的Note数组的位置。 创建Note节点

    note中存储了key的hash值,键值对,以及对下一个note节点的引用,由此可见,这是一个单向链表的结构

    我们put数据的时候,会有两种情况,一是上面这种,数组下标节点没有数据,第二种是有值,接下来我们看第二种情况

    这里又会有三种情况:

    1. 拿当前Map中的Note节点的hash值与要put进去的key的hash判断,相等的话,再比较Map中Note节点的key与传进来的key地址是否一致,如果一致,则将值覆盖,不一致就再继续进行key的equals比较,为true就覆盖,不然继续判断。
    2. 判断当前节点是否是一个红黑树结构,如果是的话,将数据存储到树节点中。
    3. 如果都不满足,则将数据添加到链表中。说的很简单,我们继续看一下代码:

      p = 当前数组位置的元素
      e = 当前数组位置的元素的链表的下一个

    先判断当前节点链表是否有下一个,如果没有,则初始化Note节点,加入到链表中,再判断链表的长度,有没有达到转换树结构的阀值,达到了则将链表转换为树结构。
    如果当前节点链表有下一个,判断节点数据的hash值与传进来的hash是否相等,相等则判断节点的key与传进来的key的地址值比较和equals比较,满足任何一条,就退出。不满足就继续迭代当前数组位置的链表,执行前面的逻辑。


    最后把Map的Size加1, 判断是否需要扩容。

    resize() 方法中还有许多逻辑判断,喜欢研究的自己去看吧,作者太菜,不爱看算法

    相关文章

      网友评论

        本文标题:JDK1.8 HashMap源码解读 一一put方法

        本文链接:https://www.haomeiwen.com/subject/zxmqsftx.html