我正在参与掘金创作者训练营第4期(带链接:juejin.cn/post/706419…
有时候当我们写完代码 检查代码时是不是会发现这样的提示?
于是我们这样修改下
是不是已经完全没有问题了?
写了2和不写真的会有很大的差别吗?
带着这俩问题我又发现了以下提示信息...
到这里结束?当然没有,我们强大的好奇心必然会驱使我们发现为啥是16 而不是2、4或者8?
于是我们可以查看下HashMap源码:
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
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方法
/**
* Returns a power of two size for the given target capacity.
*/
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;
}
乍一看,我并不知道我在哪?我在干啥? 于是想到了个笨的方法来执行下这个方法
//测试tableSizeFor方法 这边我把HashMap类中的静态方法放在了本地类里面
Map data = new HashMap<>();
for (int i = 0; i <30 ; i++) {
data.put(i,tableSizeFor(i));
}
System.out.println(JSON.toJSONString(data));
执行结果如下:
{
"0": 1,
"1": 1,
"2": 2,
"3": 4,
"4": 4,
"5": 8,
"6": 8,
"7": 8,
"8": 8,
"9": 16,
"10": 16,
"11": 16,
"12": 16,
"13": 16,
"14": 16,
"15": 16,
"16": 16,
"17": 32,
"18": 32,
"19": 32,
"20": 32,
"21": 32,
"22": 32,
"23": 32,
"24": 32,
"25": 32,
"26": 32,
"27": 32,
"28": 32,
"29": 32
}
是不是突然悟了 返回的结果就是比当前值最接近(>=)的2的n次幂
以上只是总结规律,并不能说我们已经读懂了那段代码
介绍下 |= 运算符
//举例: 以下是完全等价的
a |= b
a = a | b
运算逻辑:
int a = 5; // 用8位2进制表示为 0000 0101
int b = 13;// 用8位2进制表示为 0000 1101
a |= b; // 用8位2进制表示为 0000 1101 a = 13
>>> 无符号右移。无论是正数还是负数,高位通通补0
int i = 3 >>> 1; //结果为 1
// 3 表示为2进制数为:0011 往右位移1位高位补0 得到0001 所以结果为1
代码具体分析
static final int tableSizeFor(int cap) {
int n = cap - 1; //假设n为0100000000000001 减一之后为 0100000000000000
n |= n >>> 1; 位移1位置 得到 0010000000000000 |=之后 为 0110000000000000
n |= n >>> 2; 位移2位置 得到 0001100000000000 |=之后 为 0111100000000000
n |= n >>> 4; 位移4位置 得到 0000011110000000 |=之后 为 0111111110000000
n |= n >>> 8; 位移8位置 得到 0000000001111111 |=之后 为 0111111111111111
n |= n >>> 16; 位移16位置 得到 0000000000000000 |=之后 为 0111111111111111
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
这个过程其实就是往低位的0位上补1
而第一步的减1操作 是为了防止当前传的cap值正好是2的幂次方
举例:
static final int tableSizeFor(int cap) {
int n = cap; //假设n为8 2进制表示为 1000 这边不减1
n |= n >>> 1; 位移1位置 得到 0100 |=之后 为 1100
n |= n >>> 2; 位移2位置 得到 0011 |=之后 为 1111
n |= n >>> 4; 位移4位置 得到 0000 |=之后 为 1111
n |= n >>> 8; 位移8位置 得到 00000000 |=之后 为 00001111
n |= n >>> 16; 位移16位置 得到 0000000000000000 |=之后 为 0000000000001111
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
//返回 n+1 即:0000000000001111 加1 等于 0000000000010000
//10进制则表示为16 结果显然不是预期的
}
到这里 其实我们就明白了 这是为了防止最后n+1的时候升位 所以在n开始的时候 先减1
好了这样我们就知道了new HashMap(n) 的n需要放什么值了。
至于为什么要初始化这个值,简单点说就是为了性能。