hashmap和ConcurrentHashMap的初始化和扩容

hashmap

关于初始化和扩容

首先查看初始化的相关源码

jdk17


this.loadFactor = loadFactor;  
this.threshold = tableSizeFor(initialCapacity);


初始化围绕着上面两个参数进行,
1,initialCapacity不得超过MAXIMUM_CAPACITY = 1 << 30的2的幂
2,loadFactor的默认值是0.75,构造函数指定的值只需要非空在0-1之间有效即可

3,构造函数并没有真的初始化hashmap内部用于存储数据transient Node<K,V>[] table;,而是在put时进行检查
if ((tab = table) == null || (n = tab.length) == 0)  
    n = (tab = resize()).length;
    
    
    

扩容核心方法resize(),他在put时调用

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)   
    newCap = oldThr;  
else {                
    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);  
}

oldCap的来源是当前oldTab.length
oldThr来自初始化指定的扩容大小threshold,也就是源码注释中称为要调整大小的下一个尺寸值(容量 * 负载系数)。

1, 初始化(

调用位置在put第一时间进行检查

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,  
               boolean evict) {  
    Node<K,V>[] tab; Node<K,V> p; int n, i;  
    if ((tab = table) == null || (n = tab.length) == 0)  
        n = (tab = resize()).length;
        xxxxxxxxxx
        xxxxxxxxx
        }

默认情况下(空参构造器),threshold=0,loadFactor = 0.75,

 newCap = DEFAULT_INITIAL_CAPACITY;  
 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)

指定了threshold,通过另外两个有参构造器指定

newCap = oldThr;

2,扩容,每一次put结束后都会进行容量检查

if (++size > threshold)  
    resize();

此时由于初始化完成,oldCap > 0必然成立,所以只会走到第一个if判断内部

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  
}

混淆点,hashmap所谓的容量是哈希表的容量,而不是内部总元素的容量,具体来说,他是针对于检索效率的一种优化,这和处理哈希冲突的拉链法和红黑树转换没什么区别。
但是触发扩容++size > threshold,其中的size确实总元素数量,在计算hash时e.hash & (newCap - 1),也就是说,对哈希表中元素进行重新打算,此时可以注意到,当 threshold<MAXIMUM_CAPACITY时,哈希表中元素数量永远小于哈希表的长度(负载因子小于1, threshold=负载因子*哈希表长度),那么显然,如果离散函数保证不发生hash冲突,那么集合的大部分方法(put、add、continals)都是O(1),当查询到哈希冲突的链表是时间复杂度是(O)n,(这个n也不是总元素,而是链表长度),当查询到哈西冲突的是红黑树时,时间复杂度是O(log n)。

对应力扣题: https://leetcode.cn/problems/insert-delete-getrandom-o1/description/?envType=study-plan-v2&envId=top-interview-150

ConcurrentHashMap

关于初始化和扩容

相关参数

表初始化和调整大小控制。当为负数时,表正在初始化或调整大小:-1 表示初始化,否则 -(1 + 活动调整大小线程数)。否则,当 table 为 null 时,保存创建时要使用的初始表大小,或默认值为 0。初始化后,保存要调整表大小的下一个元素计数值
private transient volatile int sizeCtl;

构造函数的参数和重载和hashmap相同,但ConcurrentHashMap的初始化只干预了sizeCtl,对于负载因子他并没有保存,同时在put中提供了专门的initTable()

posted @ 2025-10-11 09:45  wenzhuo4657  阅读(2)  评论(0)    收藏  举报