1.前言
本片文章主要讲一下hashMap 中的 put 方法,主要是用来向hashMap中放入元素的相关操作
2. 源码
public V put(K key, V value) {
// 先获取当前key 的hash 然后调用的 putval方法,
return putVal(hash(key), key, value, false, true);
}
// 获取hash值的方法
static final int hash(Object key) {
int h;
// 按位异或运算符(^)是二元运算符,要化为二进制才能进行计算,
// 在两个操作数中,如果两个相应的位相同,则运算结果为0,否则1
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.1 putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) 详解
/**
* @param hash key的hash值
* @param key 存储的key
* @param value 存储的value
* @param onlyIfAbsent 当这个为true的时候,若当前key在hashmap 中有值(不为null),则不会修改旧值
* @param evict 如果它是false 表示hashmap的表处于创建模式
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
/**
tab : 创建临时节点表
p : 临时节点
n : hashMap.table 的长度
i : 记录table的索引
*/
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 将hashMap的table 赋给临时变量 table , 当 tab == null 时 调用 resize()方法进行初始化
// 当 tab != null 接着判断 tab的长度也不能为0 , 否则还是调用 resize() 方法进行初始化。
// 这里 resize() 方法我在前面讲过,链接贴在上面了
if ((tab = table) == null || (n = tab.length) == 0)
// 把初始化后的 table 赋给 tab , table的长度赋给 n
n = (tab = resize()).length;
// (n - 1) & hash 这里确定当前元素插入的位置,这里为什么是(n-1)&hash 也在 resize()方法中讲过
// 当这个地方的元素为null时,则说明以前没有放元素进来,这里可以直接插入
if ((p = tab[i = (n - 1) & hash]) == null)
// 在索引为i的位置,创建一个新节点直接放在这里即可
tab[i] = newNode(hash, key, value, null);
else { // 这里是处理当前位置已经有元素的情况下,可能是以链表的形式放入,也可能是以红黑树的形式放入
Node<K,V> e; K k;
// 这里即 p已经保存了 tab[i = (n - 1) & hash]上的元素,且不为null
// p的hash 和 插入的hash相等 且 p的key等于 插入的key(这里等于表示 == 或者重写equals返回true)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 定义的节点e 保存 节点 p
e = p;
// 判断节点p 是否是一个红黑树
else if (p instanceof TreeNode)
// 如果是一个红黑树,则调用红黑树的 putTreeVal 方法,详解见上方链接
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 否则的话,说明它是一个链表结构,则遍历链表,这里bitCount 记录的是链表长度,
// 当链表长度 >= 7 会将该链表进行树化
for (int binCount = 0; ; ++binCount) {
// 如果 p.next == null ,则直接新建一个节点并让p.next 指向它
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果链表长度 >= 7 ,调用 treeifyBin(tab, hash) 进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 树化 详解见 2.2
treeifyBin(tab, hash);
// 树化后,跳出出循环
break;
}
// 这里判断,当e.hash和传入hash相同时,且他们的key也相同,则跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 当e != null ,说明这个key对应的映射关系已经存在
if (e != null) { // existing mapping for key
// 取出原先的值
V oldValue = e.value;
//当onlyIfAbsent 为false 或 oldValue == null时,会对旧值进行覆盖
if (!onlyIfAbsent || oldValue == null)
// 把e的value设为新传入value
e.value = value;
// 这个方法是一个后置处理器,在HashMap中是空实现
// 在LinkHashMap 中有默认实现
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
}
// modCount:记录hashMap修改的次数
++modCount;
// size:hashMap的size ,当它 大于 threshold(达到它就会扩容,详解可见上方链接)时 会进行扩容操作
if (++size > threshold)
resize();
// 也是一个类似于后置处理器的null 方法,在LinkHashMap 中有默认实现
afterNodeInsertion(evict);
return null;
}
2.2 treeifyBin() 方法详解
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 这里会判断,当数组的长度 < 64时,即使链表长度超过7 也不会树化,会调用resize()方法进行扩容,详解见上方链接
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 这里取出当前元素要插入的数组中的索引位置记为 index,取出tab[index] 元素记为 e,判断当它不为null时进入 else if
else if ((e = tab[index = (n - 1) & hash]) != null) {
// hd : 链表头结点; tl:链表尾结点
TreeNode<K,V> hd = null, tl = null;
// 这里做一个循环,遍历链表e
do {
// 创建一个节点p ,详解见下面
TreeNode<K,V> p = replacementTreeNode(e, null);
// 如果尾结点是null,就把 p赋给头结点
if (tl == null)
hd = p;
// 如果尾结点不是null,就把尾结点设为p的前置节点,尾结点的next指向p
else {
p.prev = tl;
tl.next = p;
}
// 尾结点设为p
tl = p;
} while ((e = e.next) != null);
// 这里判断 hd != null 时 调用treeify()方法进行树化,详解见上方链接
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
// 通过节点p 新建一个节点,并使它的next 指向 next节点。
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<>(p.hash, p.key, p.value, next);
}