前言:没想到短短的一个put方法,竟然有这么多逻辑。。。
Hello,昨天大概讲了下HashMap中几个重要的默认值以及HashMap的构造函数,今天主要想看一下存储数据的过程,即HashMap中put方法的实现。
public V put(K key, V value) {
//1,先将key进行hash扰动
//2,调用putVal方法
return putVal(hash(key), key, value, false, true);
}
//存储所有元素的数组,默认为null
transient Node<K,V>[] table;
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
//判断table如果为空,调用resize方法初始化table
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(n - 1) & hash 计算出来的值是在0->n之间的数,即不会超过数组的长度
//如果当前数组下标的数据为空,则将当前元素直接存放到i下标位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//1,如果table[i]位置已经存在元素,
//则判断当前传入的key与当前i位置元素的key是否完全一致,如果一致,
//则直接运行到后面去覆盖value值
//如: map.put("1","1"); 后再次:map.put("1","2");
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))){
e = p;
} else if (p instanceof TreeNode){
//2,判断当前table[i]位置的元素如果它已经是树结构,则当前key,value就需要存入到TreeNode中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
}else {
//3,最后就只有链表这种情况了,那就循环遍历链表吧
for (int binCount = 0; ; ++binCount) {
//如果找到链表的最后一个元素,即p.next == null
//那就在链表最后追加一个新节点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//判断链表长度是否大于等于8,如果是就要进行树化
if (binCount >= TREEIFY_THRESHOLD - 1){
treeifyBin(tab, hash);
}
break;
}
//在遍历过程中,判断链表中元素的key,
//如果与当前传入的key相同,则会跳出循环
//注意循环中的e = p.next,此处跳出循环后e仍旧是有值的
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))){
break;
}
//继续下一个链表元素进行判断
p = e;
}
}
//这里就是value值覆盖操作啦
//如果有在数组中找到元素,则需要将当前传入的value覆盖旧的oldValue
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果table的长度超过了容器设置的扩展阈值,就会进行扩容
if (++size > threshold){
resize();
}
afterNodeInsertion(evict);
return null;
}
代码可能看得有点晕,画一个顺序图吧:
总结:今天大致梳理了一下put方法的执行过程。但依旧只是皮毛,没有深究到树化方法的逻辑(treeifyBin),以及树结构put方法存储value的逻辑(putTreeVal),今天就到这吧。我是阿雷,一个逐渐熬夜的程序员。