HashMap扩容机制源码撕起来(二)。

前言

    HashMap是我们日常工作中接触较多的集合之一,HashMap是线程不安全的,多线程环境下,建议使用ConcurrentHashMap。
    此处想到一个面试题,问:HashMap是怎么表现其线程不安全的?当时我差点就懵逼了,仔细想了一下,不安全?那肯定和多线程有关系!答:只有一个线程的时候HashMap是安全的,但是在多线程情景下,HashMap作为共享变量时是线程不安全。
    面试必问问题HashMap底层原理,以下内容是个人对JDK8下HashMap的扩容机制的见解,如有错误,敬请指正
    红黑树过于复杂,本文将略过红黑树部分奔着红黑树来的,可以移步了,对不起

扩容机制

红黑树过于复杂,不进行详细研究。

//说明:oldCap=旧桶长度,oldThr=旧桶阈值(数组长度*加载因子)
final Node<K,V>[] resize() {
	Node<K,V>[] oldTab = table;
	int oldCap = (oldTab == null) ? 0 : oldTab.length;
	int oldThr = threshold;
	int newCap, newThr = 0;
	//旧数组长度>0
	if (oldCap > 0) {
		//判断旧桶长度是否≥最大允许长度(1<<30),如果是就让threshold=1<<31,并返回旧数组
		if (oldCap >= MAXIMUM_CAPACITY) {
			threshold = Integer.MAX_VALUE;
			return oldTab;
		}//给新桶长度赋值为旧桶长度*2,并判断是否<最大允许长度(1<<30),并且大于等于1<<4(16),如果是,则newThr=oldThr*2
		else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
			newThr = oldThr << 1; // double threshold
	}
	//以下旧桶长度=0的场景
	//旧桶阈值>0,那么新桶长度=旧桶阈值
	else if (oldThr > 0) // initial capacity was placed in threshold
		newCap = oldThr;
	//旧桶长度=0 旧桶阈值=0,初始化newCap=16 newThr=12
	else {               // zero initial threshold signifies using defaults
		newCap = DEFAULT_INITIAL_CAPACITY;
		newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
	}
	//上面都走完。newThr如果为0,则重新计算新容器阈值(新桶长度*加载因子)
	if (newThr == 0) {
		float ft = (float)newCap * loadFactor;
		newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
				  (int)ft : Integer.MAX_VALUE);
	}
	threshold = newThr;
	@SuppressWarnings({"rawtypes","unchecked"})
	Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
	table = newTab;
	//如果旧桶不是空的,则要进行数据迁移
	if (oldTab != null) {
		//遍历旧桶
		for (int j = 0; j < oldCap; ++j) {
			Node<K,V> e;
			//如果旧桶根节点不是空的,则置为空
			if ((e = oldTab[j]) != null) {
				oldTab[j] = null;
				//判断是否有next节点,如果没有则重新计算在新桶中的下标
				if (e.next == null)
					newTab[e.hash & (newCap - 1)] = e;
				//如果有next节点,判断是否是红黑树节点
				else if (e instanceof TreeNode)
					((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
				//以下是链表节点的情况
				else { // preserve order
					Node<K,V> loHead = null, loTail = null;
					Node<K,V> hiHead = null, hiTail = null;
					Node<K,V> next;
					do {
						next = e.next;
						//判断该节点的hash值和旧桶长度进行与运算是否为0,
						//如果是0,
						if ((e.hash & oldCap) == 0) {
							//判断loTail是否为空,如果为空则将该节点放入loHead,否则loTail.next = e,最后将e放入loTail
							if (loTail == null)
								loHead = e;
							else
								loTail.next = e;
							loTail = e;
						}
						//如果不是0
						else {
							//判断hiTail是否为空,如果为空则将该节点放入hiHead,否则hiTail.next = e,最后将e放入hiTail
							if (hiTail == null)
								hiHead = e;
							else
								hiTail.next = e;
							hiTail = e;
						}
					} while ((e = next) != null);
					//如果loTail不为空,则将loHead数据放入新桶下标相同的位置
					if (loTail != null) {
						loTail.next = null;
						newTab[j] = loHead;
					}
					//如果hiTail不为空,则将hiHead数据放入新桶下标为在旧桶中下标+旧桶长度的位置
					if (hiTail != null) {
						hiTail.next = null;
						newTab[j + oldCap] = hiHead;
					}
				}
			}
		}
	}
	return newTab;
}

总结

    1.当新加入一个元素后,若map的size大于阈值时,进行扩容。
    2.扩容机制:获取map中的元素,如果元素存在数组上,则根据新数组长度重新计算下标(hash & (n-1)),并保存;
    如果元素存在链表上,则将key的hash值和旧数组长度进行与运算(e.hash & oldCap),如果为0,则放入新数组对应下标j的位置;
        如果不为0,则放入新数组下标为j+oldCap(旧下标+旧数组长度)位置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值