Integer.highestOneBit(int i)的实现逻辑与算法的深度刨析

首次看到这个方法是在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 经过右移一次就实现了最大数后全为121 = 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的幂次方的最大数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值