浅谈 HashMap(二):put 插入方法源码分析
本文基于 JDK1.8
这节讲讲 HashMap 的 put 方法,看似简单的一个存值方法,实则细节满满,且听我娓娓道来
话不多说,先看看源码👇
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
方法内部又调用了一个 putVal 方法,一共 5 个参数,我们先猜猜这几个参数是干嘛的。第一个参数是根据 key 调用了hash()
方法,此参数应该是用来确定 key 在 table(这个属性参照上一节)数组中的位置的;第二个参数就是传入的 key 了;第三个参数即传入的 value 了,下面我们通过源码来看看👇
/**
* Implements Map.put and related methods
*
* @param hash key的hash值
* @param key 键
* @param value 准备插入的值
* @param onlyIfAbsent 如果为true,则不修改已存在的值
* @param evict 如果为false,则table数组处于创建模式
* @return 旧的值,如果没有则返回null
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 定义局部变量,接收table数组
// 这种全局变量赋值给局部变量操作的写法,是一种编码习惯,便于代码可读性及调试
Node<K,V>[] tab;
// 局部变量,接收table数组位置上的Node节点(如果已存在节点)
Node<K,V> p;
// 局部变量 n,table数组长度
// 局部变量 i,根据hash值(由key计算而来,后续讲)确定在table数组中的位置
int n, i;
// 上节讲到table数组在第一次使用时初始化,源于此处
// 如果table数组为空,则通过resize方法初始化
if ((tab = table) == null || (n = tab.length) == 0)
// resize方法后续有专题讲
n = (tab = resize()).length;
// 判断当前table数组位置是否已有值
// 如何根据hash值确定在table数组中位置的呢?后续有专题讲
if ((p = tab[i = (n - 1) & hash]) == null)
// 如果table数组此位置没有值,则新建Node节点存于此处即可
tab[i] = newNode(hash, key, value, null);
// 如果table数组此位置已经有值,则执行下面代码
else {
// 局部变量,接收已存在的Node节点
Node<K,V> e;
// 局部变量,接收键(key)
K k;
// 判断key值是否相等,相等则将原Node赋值给局部变量 e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 判断已有节点的存储形式是否为 tree(红黑树),如果是tree类型,则给红黑树中新增一个Node(红黑树的增删此处不讲)
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 除去上面两种情况,则table数组此位置的节点为链表存储形式
else {
// 从此处循环可知,HashMap在JDK1.8的链表插入为 [尾部插入]
for (int binCount = 0; ; ++binCount) {
// 循环遍历链表,直到最后一个Node,新建一个Node存值
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 当链表长度大于 树转换阈值(8)时,链表转换为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 遍历链表时,判断链表中已存在Node的key是否与插入的key相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 判断插入的key是否为已存在的key
if (e != null) { // existing mapping for key
// 从此处可知,前面为何要定义两个局部变量 p 和 e,为了返回旧的value值
V oldValue = e.value;
// 根据传入的参数,决定是否修改已存在的值
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
// 如果插入的key已存在,返回已存在的旧的value值
return oldValue;
}
}
// HashMap的全局变量,每次操作时加 1
++modCount;
// 先对HashMap的容量 +1,判断是否达到扩容阈值(最大容量 * 负载因子0.75)
if (++size > threshold)
// 扩容,重置HashMap大小,后续有专题讲解
resize();
afterNodeInsertion(evict);
// 如果插入的key是新的,则返回null;对比上面,如果key已存在,则返回旧的value
return null;
}
put 方法的源码如上,笔者已经添加了详细的注释。其中还有一些没有展开讲的后续出专题
主要有:如何扩容 resize?hash(key) 确定数组中位置的实现方式?
作者:超级鲨鱼辣椒
转载请注明原文链接:https://www.cnblogs.com/jinzlblog/p/15118541.html