回顾下,在上一章中,分析了为什么HashMap的容量是2的指数幂。是为了key的数组寻址时,做取模运算改为位运算提高效率。
这次就来看看HashMap是如何确保容量一定是2的指数幂的。
Map<String, String> normalMap = new HashMap<>();
Map<String, String> initialCapacityMap = new HashMap<>(11);
这是HashMap比较常用的两种初始化方法,第一种是指定默认的无参构造,第二种是指定容量的构造。
看到这里可能会有疑问了,第二种明明是指定了容量11呀,并不是2的指数幂,这是怎么回事呢?接着往下看。
首先看无参的构造方法:
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
可以看到非常简单,仅仅是指定了默认的装载因子,这个装载因子和默认容量会在后续学习中继续探讨。接下来看指定容量的构造:
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
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;
}
这里就是确定HashMap初始容量的核心代码了,可以看到大量的位运算。
首先是通过校验初始容量大于0和无符号的右移位运算,确保最后的结果是正整数。
上面的位运算可能看起来一脸懵逼,其实原来分析起来也不难。下面就来一步步分析上面的位运算是怎么回事,这里用方向推导的方式进行分析。
1、我们都知道,2的指数幂用二进制表示就是0100 0000这样,即v=2^(n-1),当前例子为2的7次方,即64;
2、而64-1即63用二进制表示呢就是0011 1111,可以看到是比64低一位的全1二进制。
所以上述代码的二进制运算就是通过位移和二进制或运算,得出一个2的指数幂减1的数,再加1从而得出这个2的指数幂。