Java:HashMap源码解析
构造方法
构造方法
1、无参构造方法HashMap()
public HashMap() {
//只指定了负载因子:默认的负载因子0.75。所有其他字段默认
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
2、指定初始容量的构造方法HashMap(int initialCapacity)
public HashMap(int initialCapacity) {
//初始容量为给定的int类型的数值,实际初始化的容量并不一定是给定的容量大小,实际的容量在扩容方法中说明
//负载因子为默认负载因子0.75
//调用指定初始化容量和负载因子的构造方法
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
3、构造方法HashMap(int initialCapacity, float loadFactor)
//指定初始容量和负载因子的构造方法
public HashMap(int initialCapacity, float loadFactor) {
//如果给定的初始化容量小于0,抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
//int 类型的最大值2^31-1,入参为int类型的整数,所以给定的初始容量可能大于2^30(HashMap的最大容量)
//如果给定的初始容量大于2^30,则将初始容量设置为2^30
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//如果负载因子小于等于0或者负载因子是一个NaN(Not-a-Number),抛出异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
//将传入的参数赋值给全局变量
this.loadFactor = loadFactor;
//调整初始化容量为大于等于且最接近给定的初始化容量的的二的整数次幂。
//例如给定的初始化为17,那么会将初始化容量调整为32,32是2的5次幂,32是大于17并且是2的整数次幂中最接近17的
// 0 <= initialCapacity <= 2^30
this.threshold = tableSizeFor(initialCapacity);
}
调整初始化容量大小tableSizeFor(initialCapacity)
该方法在JDK11中与JDK1.8中的实现不一样。在JDK1.8中对n进行和五次右移和异或运算。
在JDK11中,调用的是Integer中的numberOfLeadingZeros(int i)方法,获取了n的二进制补码中最高位之前0的个数,对-1进行了一次右移。
在numberOfLeadingZeros(int i)方法中,没有了异或操作,并且对 i 右移的操作次数根据 i 的值决定,最多5次,最少1次。虽然对 1 进行了 4次左移,但是对1 进行左移操作效率比对一个随机整数进行右移操作效率要高。
该方法从JDK1.8到JDK11的升级提高了效率。
JDK11
该方法是为了将HashMap的容量调整为2的整数次幂。
//在调用该方法之前对对初始容量做了校验,所以调用该方法时,初始容量范围 0<= cap <= 2^30
static final int tableSizeFor(int cap) {
/** 当 cap = 0时,传入numberOfLeadingZeros(int i)的值为-1,numberOfLeadingZeros(int i)的返回值为0。
* 此时n = -1,经过三目运算符,tableSizeFor(int cap)的返回值为 1。
* 即初始化时传入0,初始容量会变成1
*/
//当 0 < cap <= 2^30,numberOfLeadingZeros(int i)的 入参是一个小于2^30的非负整数
//所以入参的补码的最高位之前至少有2个0。所以-1最少也要右移两位变成一个正数,并且-1右移之后的二进制数从最高位(左侧的第一个1)到最低位全为1
//当且仅当入参为2^30-1时,n = 2^30-1
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
//在最后做运算时 -1 <= n <= 2^30-1。是否可以去掉第二个三目运算符?
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
JDK1.8
该方法是为了将HashMap的容量调整为2的整数次幂
static final int tableSizeFor(int cap) {
// -1 <= n <= 2^30-1,且n为整数
int n = cap - 1;
/**
* 当 -1 < n <= 2^30-1时,该运算将n从左边第一个1到最低位都变成1,最后三目运算中,将n变成2的整数次幂
* 当 n = -1时,经过以下运算后n是一个二进制补码全部为1的二进制数,即n = -1,最后在三目运算后 n = 1.
*/
n |= n >>> 1;//等价于 n = n | n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
//判断当n小于0时,设置初始化容量为1
//当n大于等于0时,判断当n大于等于2^30时,设置初始容量为2^30
//当n大于等于0并且小于2^30时,设置容量为n+1
//经过运算可以确保返回值是大于等于1并且小于等于2^30的2的整数次幂
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}