当前位置: 首页 > 工具软件 > 升级树 > 使用案例 >

你真的知道HashMap扩容条件和红黑树升级退化的条件吗?

暨正真
2023-12-01


缘由

 写这篇文章真的不是为了水博客,作者本人正在写java数据结构相关的文章,也在积极找工作和准备面试,看到好多面试视频里对于 HashMap 扩容和红黑树升级退化的条件说的含糊其辞,于是做了一个汇总。Talk is cheap, I will show you the code.


HashMap 扩容条件

  • 当 HashMap 中键值对 key-value 个数大于阀值的时候(注意不是什么桶或数组的占用情况)
  • 升级成红黑树时,数组长度小于64,会进行一次扩容代替升级
  • HashMap 数组为空或者数组长度为 0

- 扩容流程链接

在执行 put 方法时

//在put时会执行putVal()方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    ... ...
	//数组为空,或者数组长度为0会扩容
	if ((tab = table) == null || (n = tab.length) == 0)
	    n = (tab = resize()).length;
	... ...
	//键值对的个数大于阀值会扩容,这个size是HashMap的成员变量表示k-v映射的数量   
	if (++size > threshold)
	    resize();
	... ...
}
/**
 * The number of key-value mappings contained in this map.
 */
transient int size;

红黑树发生升级会先检查条件

//升级红黑树时
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    //数组长度小于64,会进行一次扩容代替升级
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if() {
    //链表升级红黑树
    }
}


红黑树升级条件

  • 链表长度大于8(隐含条件是 Node 数组长度大于等于64 ,不满足则会发生一次扩容代替升级)
static final int TREEIFY_THRESHOLD = 8;

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ......
		/*
		 *  p是链表的头结点,for循环的第一次, e = p.next 的时候 e 指向链表的第二个元素,
		 *  此时 binCount  = 0,下面升级红黑树的条件是 binCount >= 7。for循环继续走下去,
		 *  在链表中第九个元素为空的时候 binCount = 7,此时第九个元素刚刚已经插入元素。
		 *  那么链表长度大于8时执行treeifyBin()方法,会先判断Node 数组长度大于等于64
		 *  不满足则会发生扩容代替升级,源码在 红黑树升级条件 的标题的紧挨着的上面
		 */
        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;
            }
        }
        ......
}


红黑树退化的条件

  • 扩容 resize( ) 时,红黑树拆分成的 lo 和 hi 两颗红黑树,每一颗树的结点数小于等于临界值 6 时退化成链表。 - 对于拆分成 lo 和 hi 两颗红黑树不太了解的可以点这里
  • 移除元素 remove( ) 时,在removeTreeNode( ) 方法会检查红黑树是否满足退化条件,与结点数无关。如果红黑树根 root 为空,或者 root 的左子树/右子树为空,root.left.left 根的左子树的左子树为空,都会发生红黑树退化成链表。
//扩容resize时,会把红黑树拆分成hi和lo两颗红黑树,
//hc和lc分别是highCount和lowCount的缩写,表示两颗树的结点个数
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
	... ...
	//lc小于等于6,也就是lo这颗树的结点个数小于等6发生退化
	if (lc <= UNTREEIFY_THRESHOLD)
		tab[index] = loHead.untreeify(map);
	... ...
	//hc小于等于6,也就是hi这颗树的结点个数小于等6发生退化
	if (hc <= UNTREEIFY_THRESHOLD)
		tab[index + bit] = hiHead.untreeify(map);
	... ...
}

//remove时
//如果红黑树根root为空,或者root的左子树/右子树为空,
//root.left.left根的左子树的左子树为空,红黑树会退化成链表
if (root == null
    || (movable
        && (root.right == null
            || (rl = root.left) == null
            || rl.left == null))) {
    tab[index] = first.untreeify(map);  // too small
    return;
}
	



HashMap知识点总结(附源码分析链接)

 类似资料: