首次看到这个方法是在JDK 1.7 HashMap 数组的扩容中,JDK8 HashMap的数组扩容,但自己也是利用了这种算法该方法的作用是找出给定int数的,小于等于该数值的2的最大次方数,比如给定9,返回8…
看源码,利用两个位运算符,位移和或 。代码非常之少,那么这个算法是怎么设计出来的呢?实现依据是什么呢?
publicstaticinthighestOneBit(int i) {
// HD, Figure 3-1
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return i - (i >>> 1);
}
JDK8 HashMap 求大于等于给定数的最小2的次方数
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;
}
一、定理:2的幂次方数的二进制数中,有且只有一位为1,其它全为0
如下给出的例子所示,证明:
0000 0000 = 0
0000 0001 = 1 = 2^0
0000 0010 = 2 = 2^1
0000 0011 = 3
0000 0100 = 4 = 2^2
0000 0101 = 5
0000 00110 = 6
...
①、如果存在一个n位的二进制数,那么转十进制:**a1*2^0 + a2 *2^1 + ... + an*2(n-1) 其中,(a1, a2 , ... , an )∈ {0, 1}**
②、由第一项的表达式,当 **a1 -> an** 只有一位为1,其它都为0,可以简化为 an*2(n-1),且an = 1, =》2^(n-1),一定是2的幂次方数,所以二进制数只有一位为1时,一定是2的幂次方数
③、当一个二进制数不止有1位为1时,这个数一定不是2的幂次方数,假设 n位二进制数中有m位为1的数,
a1*2^0 + a2 *2^1 + ... + an*2(n-1) 就可以把位为0的略去,只计算位为1的m个数,am1*2^am1n + am2 *2^am2n+ ... + amm*2^ammn
其中 am1, am2 ... amm)= 1, am1n -> ammn 为这个这几个位为1的未知数所在的位置, 且 am1n -> ammn 为无规律的升序,因为是升序,所以
am2n = am1n + (am2n - am1n)
am3n = am1n + (am3n -am1n)
....
ammn = am1n + (ammn -am1n)
公式又可以化为:2^am1n *( 1 + 2^(am2n -am1n) + ... + 2^(ammn -am1n)) 所以该值一定不是2的幂次方数 (2的幂次方 + 1 一定不是2的幂次方)
④、综上所述:2的幂次方数的二进制数,有且只有一位为1
⑤、例子 22 的二进制表示法 0001 0110
0001 0110
= 0*2^0 + 1*2^1 + 1*2^2 + 0 + 1*2^4 + 0 + 0 + 0
= 1*2^1 + 1*2^2 + 1*2^4
= 2^1 + 2^2 + 2^4
= 2^1 * (1 + 2^(2-1) + 2^(4-1)) 【提取公因式】
二、基于定理的实现逻辑分析与实现
(1)由定理:2的幂次方数的二进制数中,有且只有一位为1,其它全为0,,所以给定的一个数的二进制表示法 **** **** ,如果我们能保留该进制数的最大位1,并且让其它位为0,那么该数一定是小于等于 该数的2的最大幂次方数,例:
7 = 0000 0111 => 0000 0100 = 4
8 = 0000 1000 => 0000 1000 = 8
21 = 0001 0101 => 0001 0000 = 16
(2)所以,思路就来了,我们只需要保留该数的二进制数的最大位1,其它位全为0就行了,,那么怎么实现这个逻辑呢,用原数 减去该数的非最大位1的其它数就可以了好像,但是这个要被去除的部分,我们是不知道大小的,也就是没有规律的,只有让他有规律,我们才好抽象出一个适用的公式
7 = 0000 0111 => 0000 0100 = 4 = 0000 0111 - 0000 0011
8 = 0000 1000 => 0000 1000 = 8 = 0000 1000 - 0000 0000
21 = 0001 0101 => 0001 0000 = 16 = 0001 0101 - 0000 0101
(3)那么怎么统一呢?看看我们现在拥有的条件,最大位1,其它未知,我们是不是可以想办法让其它位也都为1?这样减其它位,就可以保证只保留最大位的1了
7 = 0000 0111 => 0000 0100 = 4 = 0000 0111 - 0000 0011
8 = 0000 1000 => 0000 1000 = 8 = 0000 1111 - 0000 0111
21 = 0001 0101 => 0001 0000 = 16 = 0001 1111 - 0000 1111
(4)问题就来了,,我门就要想办法把原数变为最大位其他位都为1
7 = 0000 0111 => 0000 0111
8 = 0000 1000 => 0000 1111
21 = 0001 0101 => 0001 1111
(5)再看看已知条件,我们只知道存在一个最大位1,(因为 0 不是2的幂次方,原值必须大于0),这时候就用到了位移、或运算,
因为我们只能知道首位1是确认的,其它的不知道,最坏情况就是其它全为0,所以我们按最坏的情况实现,例子8 = 0000 1000
①、位移一位,并与原值进行或运算,使得得出来的值,从最大开始有两位连续为1
0000 1000 => i |= (i >> 1); => 0000 1100
*该步骤中,有的数,比如21 经过右移一次就实现了最大数后全为1, 21 = 0001 0101 => i |= (i >> 1) => 0001 1111,但实际上我们不能确定一个未知数最大位后面有几个1,所以只能按照最坏情况这个数自身就是2的幂次方数进行位移运算来进行运算*
②、现在已经至少保证最大位1开始连续两位为1了,我们再右移两位,并或运算,就可以至少保证4位
0000 1100 => i |= (i >> 2); => 0000 1111
③、已经保证有4位,再右移4位并或运算,就可以保证8位,然后再右移8位,16位.....这里我们略去,因为我们给定的值只有4位,实际上支持最大16位。。。。
④、原数,0000 1*** ... ****
0000 1111 ... 1111 - 0000 0111 ... 1111 = 0000 1000 ... 0000,该数就是小于等于0000 1*** ... ****的2的幂次方的最大数