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

建议用 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[] tab; Node 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 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)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[] tab; Node p; int n, i;),先不管他是什么意思,然后把一个 HashMap 的一个成员变量赋值给 tab,

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

JDK1.8 HashMap源码解读 一一put方法_第1张图片
我们调用不带参数的构造方法的时候,只对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节点
JDK1.8 HashMap源码解读 一一put方法_第2张图片

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

JDK1.8 HashMap源码解读 一一put方法_第3张图片

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

JDK1.8 HashMap源码解读 一一put方法_第4张图片

这里又会有三种情况:

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

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

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

JDK1.8 HashMap源码解读 一一put方法_第6张图片

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

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

你可能感兴趣的:(JDK1.8 HashMap源码解读 一一put方法)