摘要:结合HashMap源码,介绍HashMap如何确定初始化容量,其最大容量是多少。
更多关于HashMap的知识点,请戳《HashMap知识点梳理、常见面试题和源码分析》。
本文基于Java 17进行分析。
什么是HashMap的容量?容量就是HashMap中的数组大小或者桶的数量,是由 capacity 这个参数确定的。初始容量只是哈希表在创建时的容量。
大家都知道HashMap是采用的懒加载机制,也就是说在执行new HashMap()的时候,构造方法并没有在构造出HashMap实例的同时也把HashMap实例里所需的数组给初始化。那么,什么时候才去初始化里面的数组呢?答案是在第一次用到数组的时候才会去初始化它,就是在向HashMap里面添加元素的时候。而初始化数组时,它的容量是怎么确定的呢?有两种情况:
第一种是调无参构造函数初始化实例。此时默认的数组初始化长度就是16,在后续添加元素时,进行数组初始化。
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
第二种是调用带数组容量参数的构造函数。
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
或者
/**
* 如果构造函数传入的值大于该数,则替换成该数。
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30; // ①
/**
* The next size value at which to resize (capacity * load factor).
* 数组扩容的阈值
* @serial
*/
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) // ② 判断是否超过最大容量
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
显而易见,上面那个构造方法执行的时候调用的就是下一个构造方法 this(initialCapacity, DEFAULT_LOAD_FACTOR)
。当你调用带参构造器初始化一个指定数组容量的HashMap时,构造器会根据输入的参数提前计算出数组实际的长度,这个值也是在首次添加元素时起作用。计算的逻辑在函数 tableSizeFor(int cap)
中,源码如下:
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // ③ 判断是否超过最大容量
}
它对入参cap减一之后使用了无符号右移,然后进行或运算,将n-1得到的值转成2进制之后,从1的最高位开始将低位全部转化为1,再加1之后就可以得到一个2^n
的数。
HashMap的最大容量是2^30
HashMap的最大容量是多少?容量默认是16,也可以构造时传入,最大值是1<<30,即2^30,这个在源码①的注释中已经明确说明。首先必须理解操作符 <<,它是左移操作符,表示对二进制进行左移。通常情况下,1 << x 等于 2^x。
上一节中②和③所标记的代码表明,如果要存的元素数目大于 MAXIMUM_CAPACITY,HashMap方法还把数组大小capacity强制设置成 MAXIMUM_CAPACITY。
综上所述,HashMap限制数组大小最大值有两个地方,其一就是初始化时调用tableSizeFor()函数,它会将容量置为 2的幂次,并保证不超过MAXIMUM_CAPACITY。其二就是调用扩容函数resize()进行容量翻倍时。如果容量达到MAXIMUM_CAPACITY时允许再扩容,新数组的容量就是 1 << 31,这会造成整型溢出,故Integer.MAX_VALUE是HashMap的最终容量。
HashMap的最大扩容阈值是2^31-1
在扩容函数resize()中有一个强制设置阈值大小的代码片段:
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
hreshold是HashMap所能容纳的最大数据量的Node(键值对)个数,threshold = length * Load factor
。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。
在这里可以看到,其实 HashMap 扩容阈值threshold的最大值就是Integer.MAX_VALUE=2^31-1;
刷一道面试题
今天看一个关于HashMap的性能问题:如果HashMap只装载100个元素,new HashMap(int x)中x的最佳值是多少,为什么?
答案:256。
解析:题意是令加载因子取默认值0.75,此时HashMap的初始容量可以设为100/0.75 = 133.33,向上取整为134。
无论你的HashMap(int x)中的x设置为多少,HashMap的大小都是2n,而且2n是大于x的第一个数,故大于134的第一个2^n无疑是256。
结束语
以上就是这篇文章的全部内容了,希望本文对道友的学习或者工作能带来一定的帮助,如有疑问请留言交流。Wiener在此祝各位生活愉快!工作顺利!
人情早晚有用完的时候,如若自己拥有足够的实力,定能赢得别人的尊重。更何况没有人欠我们人情呢!为人处世尚且如此,披星戴月的码农是不是要刻苦钻研,拓展技术栈的广度和深度呢?