在创建HashMap的时候可以通过构造函数指定初始容量,但实际在开辟空间时有时并不会按指定的容量大小去创建,而是按大于指定容量的2的次幂的最小值去创建
对应源码如下:
指定初始容量大小的构造方法会调初始容量大小、负载因子的构造方法,通过tableSizeFor(initialCapacity)方法调整大小
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);
}
tableSizeFor方法如下:
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;
}
其中 n |= n >>> 1 是 或等于 运算,即两个二进制数相同位上的都为0时 或等于 结果为0,其他情况为1;n >>> 1是向右移位操作,移出的数字被抛弃,空出的数字补零,数字是几就代表向右移几位,不用 >> 这种移位操作是为了保证移完位后位数与移位之前保持一致
为什么先减1再参与 或等于 运算?
目的是传入cap值为2的次幂时返回它本身,而不是大于它本身的最小的2的次幂。
如果不减1,比如传入8时,会返回16,但理论上应该返回8:
public static void main(String[] args) {
int cap = 8;
//int n = cap - 1;
int n = cap;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
System.out.println((n < 0) ? 1 : (n >= 1 << 30) ? 1 << 30 : n + 1);
}
或等于 为什么要到 n >>> 16,而不是到 n >>> 32、n >>> 64?
因为容量的最大值 MAXIMUM_CAPACITY=1 << 30,即2的30次幂,30的一半是15,到 n >>> 16是为了保证极端情况下 或等于 操作结束后各二进制位全是1,最后 n+1 返回2的整数次幂。
简单演示下过程:
至于最大容量为什么定为2的30次幂,是因为容量是int类型数据,int占32位,Java的原始类型里没有无符号类型,(从右往左数)所以第32位表示符号位,1<<30即1向左移30位,1在第31位,其他位为0
为什么容量一定要调整成2的次幂大小呢?
简单来说,为的是 让容量-1 变成二进制时各位全为1,这样在和 hash 做 与运算 时会保留hash中 后 x 位的 1,从而保证元素的索引值不会超过数组长度。具体原因去问度娘吧。