0
点赞
收藏
分享

微信扫一扫

Java--HashMap扩容的原理

静悠 2022-02-15 阅读 70


简介

说明

        本文介绍Java的HashMap是如何扩容的。

重要大小




初始容量



最大容量



扩容时倍数



加载因子



底层实现



HashMap



2^4



2^30



n * 2



0.75



Map.Entry



HashSet



2^4



2^30



n * 2



0.75



HashMap<E,Object>



HashTable



11



Integer.MAX_VALUE - 8



n*2 + 1



0.75



Hashtable.Entry


        HashMap中,哈希桶数组table的长度length大小必须为2的n次方(非质数),这是一种非常规的设计,常规的设计是把桶的大小设计为质数。相对来说质数导致冲突的概率要小于非质数Hashtable初始化桶大小为11,就是桶大小设计为质数的应用(Hashtable扩容后不能保证还是质数)。

         HashMap采用这种非常规设计,主要是为了在取模和扩容时做优化。为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程。

何时扩容

        HashMap是懒加载,构造完HashMap对象后,若没用 put 来插入元素,HashMap不会去初始化或者扩容table。

        首次调用put方法时,HashMap会发现table为空然后调用resize方法进行初始化。

        非首次调用put方法时,若HashMap发现size(数组大小)大于threshold(阈值)(当前数组的大小乘以加载因子的值),则会调用resize方法进行扩容。

        数组是无法自动扩容的,所以只能是换一个更大的数组去装填以前的元素和将要添加的新元素。

resize()概述


  1. 判断扩容前的旧数组容量是否已经达到最大(2^30)了

  1. 若达到则修改阈值为Integer的最大值(2^31 - 1),以后就不会扩容了。
  2. 若没达到,则修改数组大小为原来的2倍

  1. 以新数组大小创建新的数组(Node<K, V>[])
  2. 将数据转移到新的数组(Node<K, V>[])里
  1. 不一定所有的节点都要换位置。
  1. 比如:原数组大小为16,扩容后为32。若原来有hash值为1和17两个数据,他们对16取余都是1,在同一个桶里;扩容后,1对32取余仍然是1,而17对32取余却成了17,需要换个位置。
  1. 对应的代码为:if ((e.hash & oldCap) == 0)  若为true,则不需要换位置。
  1. 返回新的Node<K, V>[] 数组

源码

HashMap#resize()

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
transient Node<K,V>[] table;
int threshold;
final float loadFactor;

final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
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;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
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;
if ((e.hash & oldCap) == 0) { // 重点1:判断节点在resize之后是否需要改变在数组中的位置
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 重点2:将某节点中的链表分割重组为两个链表:一个需要改变位置,另一个不需要改变位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}

其他网址

​​HashMap之扩容机制 - 简书​​


举报

相关推荐

0 条评论