HashMap底层原理与扩容机制


1.7 数组 + 链表
1.8 数组 + (链表 | 红黑树)

HashMap的容量变化通常存在以下几种情况:

  • 空参数的构造函数:实例化的HashMap默认内部数组是null,即没有实例化。第一次调用put方法时,则会开始第一次初始化扩容,长度为16。

  • 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值(threshold)。第一次调用put方法时,会将阈值赋值给容量,然后让 阈值=容量X负载因子(因此并不是我们手动指定了容量就一定不会触发扩容,超过阈值后一样会扩容!!)

  • 如果不是第一次扩容,则容量变为原来的2倍,阈值也变为原来的2倍。

JAVA 1.8 之后hashmap 树化规则

HashMap里面定义了一个常量TREEIFY_THRESHOLD = 8,当链表长度超过树化阈值 8 时,
先尝试调用resize()方法进行扩容来减少链表长度,如果数组容量已经 >=64(MIN_TREEIFY_CAPACITY),
才会进行树化,Node节点转为TreeNode节点(TreeNode也是HashMap中定义的内部类)。
如下代码所示:在计算当前key时候,遍历当前桶 节点Node数binCount,如果binCount大于等于TREEIFY_THRESHOLD 8,则调用treeifyBin 尝试扩容。
treeifyBin 方法中 如果桶数组长度tab.length小于MIN_TREEIFY_CAPACITY 64,则直接桶数组扩容;
如果 桶数组长度tab.length大于MIN_TREEIFY_CAPACITY 64 则Node节点转为TreeNode(红黑树)节点
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
...
int binCount = 0;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
...
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}

final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;

...
}

posted @ 2023-11-30 17:15  adam.li  阅读(12)  评论(0编辑  收藏  举报