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)。
ConcurrentHashMap
关于初始化和扩容
相关参数
表初始化和调整大小控制。当为负数时,表正在初始化或调整大小:-1 表示初始化,否则 -(1 + 活动调整大小线程数)。否则,当 table 为 null 时,保存创建时要使用的初始表大小,或默认值为 0。初始化后,保存要调整表大小的下一个元素计数值
private transient volatile int sizeCtl;
构造函数的参数和重载和hashmap相同,但ConcurrentHashMap的初始化只干预了sizeCtl,对于负载因子他并没有保存,同时在put中提供了专门的initTable()
,