JDK1.8|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 = https://www.it610.com/article/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,
JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片
JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片

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

JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片
我们调用不带参数的构造方法的时候,只对HashMap的负载因子进行了初始化,所以在这里的 threshold 的值为零,然后代码走到了 else 这里,将默认的初始化大小(16)赋值给newCap,然后计算出Map的容量达到多少之后需要扩容。 【JDK1.8|JDK1.8 HashMap源码解读 一一put方法】

JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片

这里将扩容的阀值赋值给成员变量 threshold 方便之后存取,然后new一个刚刚计算出数量的Note数组,再将Note数组赋值给成员变量table,此table就是HashMap具体存储数据的地方,接下来,oldTab依然为null,所以直接返回初始化好的Note数组-->> JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片


JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片
这里将初始化好的数组的大小赋值给 变量n,然后通过 算法(数组的长度 - 1 & key的hash值) 计算出数组的某个下标位置存储的元素赋值给 变量p,再判断这个元素是否为null,如果是null的话,创建一个Note,把note数据存储在计算好的Note数组的位置。


创建Note节点 JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片
JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片
note中存储了key的hash值,键值对,以及对下一个note节点的引用,由此可见,这是一个单向链表的结构
JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片
我们put数据的时候,会有两种情况,一是上面这种,数组下标节点没有数据,第二种是有值,接下来我们看第二种情况
JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片
这里又会有三种情况:
  1. 拿当前Map中的Note节点的hash值与要put进去的key的hash判断,相等的话,再比较Map中Note节点的key与传进来的key地址是否一致,如果一致,则将值覆盖,不一致就再继续进行key的equals比较,为true就覆盖,不然继续判断。
  2. 判断当前节点是否是一个红黑树结构,如果是的话,将数据存储到树节点中。


  3. 如果都不满足,则将数据添加到链表中。说的很简单,我们继续看一下代码: JDK1.8|JDK1.8 HashMap源码解读 一一put方法
    文章图片

    p = 当前数组位置的元素
    e = 当前数组位置的元素的链表的下一个
先判断当前节点链表是否有下一个,如果没有,则初始化Note节点,加入到链表中,再判断链表的长度,有没有达到转换树结构的阀值,达到了则将链表转换为树结构。
如果当前节点链表有下一个,判断节点数据的hash值与传进来的hash是否相等,相等则判断节点的key与传进来的key的地址值比较和equals比较,满足任何一条,就退出。不满足就继续迭代当前数组位置的链表,执行前面的逻辑。
JDK1.8|JDK1.8 HashMap源码解读 一一put方法
文章图片

最后把Map的Size加1, 判断是否需要扩容。
resize() 方法中还有许多逻辑判断,喜欢研究的自己去看吧,作者太菜,不爱看算法

    推荐阅读