在HashMap中数据是存储在哈希表中的,实际上就是一个一维数组,而哈希表的大小总是是二的整数幂,这是因为在HashMap的resize()方法也就是扩容中,对于其所形成的链表的移动是以当前哈希表数组的下标值加上原来数组长度作为扩容后的数组下标,那么其最大取值就是原来数组的两倍大小,又因为其默认的初始大小是16,故该哈希表的大小总是2的整数幂。所以在HashMap中给出了一个计算大于等于当前数值的最小二的整数次方的方法,以方便其计算容量和其他操作:
源码如下
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;
}
可以看到这个方法是针对整型数据进行的操作!
int n = cap - 1;
刚开始看到这个操作或许会有些懵,不知道为什么要减去1,实际上这只是针对二的整数幂进行的退位操作,后面我会给出解释。
先单独看这段代码:
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
假设 n = 5时
n |= n >>> 1;
0000 0101
0000 0010
0000 0111
n |= n >>> 2;
0000 0111
0000 0001
0000 0111
n |= n >>> 4;
0000 0111
0000 0000
0000 0111
...
最后无符号右移再与等结果都是
0000 0111
这样就得到了5最高非0位下的最大值
即 0000 0111
对其加一的结果就是 0000 1000
即大于5的最小二的整数幂 8
其实这个算法的思路就是将该数字的最高非0位后面全置为1!其利用了“拷贝”的方式:
n= ; 1000 0000 0000 0000 0000 0000 0000 0000
n |= n >>> 1; 1100 0000 0000 0000 0000 0000 0000 0000 将最高位拷贝到下1位
n |= n >>> 2; 1111 0000 0000 0000 0000 0000 0000 0000 将上述2位拷贝到紧接着的2位
n |= n >>> 4; 1111 1111 0000 0000 0000 0000 0000 0000 将上述4位拷贝到紧接着的4位
n |= n >>> 8; 1111 1111 1111 1111 0000 0000 0000 0000 将上述8位拷贝到紧接着的8位
n |= n >>> 16; 1111 1111 1111 1111 1111 1111 1111 1111 将上述16位拷贝到紧接着的16位
由上面可以看出其通过这五次的计算,最后的结果刚好可以填满32位的空间,也就是一个int类型的空间,这就是为什么必须是int类型,且最多只无符号右移16位!
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
其中的MAXIMUM_CAPACITY 是HashMap的最大空间为1 << 30,即2^30刚好一个G,所以HashMap大小不是取决于堆内存!
接下来就来讨论为什么要减一:
以 n = 8为例
0000 1000
最后的结果为:
0000 1111
对其加一得到的是16,显然没有把自身包含进去
若减一
n = 7
0000 0111
最后的结果为:
0000 0111
对其加一得到的是8
所以在一开始进行减一的操作是为了防止出现二的整数幂时,没有把自身包含进范围!
有了这个思路,我仿照其做了一个方法,用来找小于一个整型数的、最大的二的整数幂:
public static int getMinBinLessValue(int value) {
int resutl = value;
resutl |= resutl >>> 1;
resutl |= resutl >>> 2;
resutl |= resutl >>> 4;
resutl |= resutl >>> 8;
resutl |= resutl >>> 16;
return (resutl>>>1) + 1;
}