Integer中的奇妙位运算

Integer中的奇妙位运算

参考资料

http://www.javashuo.com/article/p-tyhrgxud-dn.htmlhtml

highestOneBit(int i)

函数的做用是得到传入参数的最高位的1,对于正数来讲返回值为小于i的最大二次幂,对于负数来讲永远是负数的最大值即-2^31java

例如:7=0000 0111(省略前24位0)那么函数的返回值为 0000 0100=4算法

暴力法

一般来讲最直观的作法就是暴力法,我一个一个数不就行了segmentfault

//一位一位取就是了
public int heigestOneBit(int i){
    int res=1;
    if (i<0)return Integer.MIN_VALUE;
    while(i!=0){
        if (i!=1){
            res*=2;
        }
        i/=2;
    }
    return res;
}

位运算

看看JDK如何利用更加高效的位操做实现这一个函数函数

public static int highestOneBit(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);
}

为何JDK一通位操做就把最高位取出来了呢?很是的巧妙啊,举例说明,看如下就知道了优化

//如下以数字为例,其中x表示0或者1不影响结果,1-最高位的1
i               0000 001x xxxx xxxx xxxx xxxx xxxx xxxx
i>>1		0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx
i|=(i>>1)	0000 0011 xxxx xxxx xxxx xxxx xxxx xxxx

i		0000 0011 xxxx xxxx xxxx xxxx xxxx xxxx
i>>2		0000 0000 11xx xxxx xxxx xxxx xxxx xxxx
i|=(i>>2)	0000 0011 11xx xxxx xxxx xxxx xxxx xxxx

i		0000 0011 11xx xxxx xxxx xxxx xxxx xxxx
i>>4		0000 0000 0011 11xx xxxx xxxx xxxx xxxx
i|=(i>>4)	0000 0011 1111 11xx xxxx xxxx xxxx xxxx

i		0000 0011 1111 11xx xxxx xxxx xxxx xxxx
i>>8		0000 0000 0000 0011 1111 11xx xxxx xxxx
i|=(i>>8)	0000 0011 1111 1111 1111 11xx xxxx xxxx

i		0000 0011 1111 1111 1111 11xx xxxx xxxx
i>>16		0000 0000 0000 0000 0000 0011 1111 1111
i|=(i>>16)	0000 0011 1111 1111 1111 1111 1111 1111

i		0000 0011 1111 1111 1111 1111 1111 1111
i>>>1		0000 0001 1111 1111 1111 1111 1111 1111
i-(i>>>1)	0000 0010 0000 0000 0000 0000 0000 0000

看完上面的简单分析应该就知道JDK如何实现的了,简单来讲就是把第一个1不断日后移动,使得从第一个1以后的全部比特位都为1,此时减去右移一位的值,也就是减去后面全部的1表明的值,此时天然只剩下第一个1了,能够说很是的巧妙了code

bitCount(int i)

该方法的做用是统计一个整数的二进制表示形式中1的个数,没记错的话这其实也是leetcode中的一道题htm

首先仍是咱们本身来思考一下如何实现:blog

暴力法

一个bit一个bit计数leetcode

public static int bitCount(int i){
    //暴力法
    int count=0;
    while(i!=0){
        if ((i&1)==1){
            count++;
        }
        i=i>>>1;
    }
    return count;
}

位运算优化一下

试想对于二进制 100,1的个数为1,按照暴力法须要3次才能统计出来,怎么样一次统计出来呢,也就是怎么一次就把100变成0呢?

对于1xxx这样的数字,x表明0,以100为例,100-1=011,而100&011刚好为0,能作多少次这样的运算,它就有多少位1,代码以下

public static int bitCount(int i){
    //位运算优化
    int count=0;
    while(i!=0){
        i=i&(i-1);
        count++;
    }
    return count;
}

到这里,有多少位的1,就统计多少次,貌似看起来已经还算不错了

JDK的位运算

public static int bitCount(int i) {
    // HD, Figure 5-2
    i = i - ((i >>> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    i = (i + (i >>> 4)) & 0x0f0f0f0f;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    return i & 0x3f;
}

Superise Mother Fxxk!这是在干什么?首先解释一下整体的思想

//要统计以下二进制的:1001 1010 1010 1010 的1的位数
//JDK是这样作的
先每两个bit统计有多少个1,而后就保存在二进制的本地
10 01 10 10 10 10 10 10
01 01 01 01 01 01 01 01
而后再统计连续四个bit有多少个1,而后保存在本地
0010 0010 0010 0010
再统计8个bit有多少个1,保存在本地
0000 0100 0000 0100
而后再统计每16个比特有多少个1,保存再本地
0000 0000 0000 1000 ==8总共8个1

有了总体的算法思想,来看看这几个奇怪的数字0x555555550x333333330x0f0f0f0f

他们对应的二进制以下:

0x55555555			01010101010101010101010101010101
0x33333333			00110011001100110011001100110011
0x0f0f0f0f			00001111000011110000111100001111

针对0x55555555来看看效果,怎么把两个相邻bit位中的1存储下来,

//以12345为例
12345				0000 0000 0000 0000 0011 0000 0011 1001
0x55555555			0101 0101 0101 0101 0101 0101 0101 0101
12345&
0x55555555			0000 0000 0000 0000 0001 0000 0001 0001
//能够看到至关于把两个相邻的比特位的后一位的1所有取出来了
12345>>>1			0000 0000 0000 0000 0001 1000 0001 1100
0x55555555			0101 0101 0101 0101 0101 0101 0101 0101
12345>>>1
&0x55555555			0000 0000 0000 0000 0001 0000 0001 0100
//能够看到至关于把两个相邻的比特位的前一位的1所有取出来了
12345				00 00 00 00 00 00 00 00 00 11 00 00 00 11 10 01
last 1				00 00 00 00 00 00 00 00 00 01 00 00 00 01 00 01
first 1				00 00 00 00 00 00 00 00 00 01 00 00 00 01 01 00
last1+fisrt1		00 00 00 00 00 00 00 00 00 10 00 00 00 10 01 01
//能够看到两位中的1的数量已经用两个bit来保存了

算法实现以下:

public static int bitCount(int i) {
    i = (i & 0x55555555) + ((i >>> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    i = (i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f);
    i = (i & 0x00ff00ff) + ((i >>> 8) & 0x00ff00ff);
    i = (i & 0x0000ffff) + ((i >>> 16) & 0x0000ffff);
    return i;
}

JDK再作点优化便可

  • 第一步,对于 11 这两个比特位来讲,用 01 + 01 的方式能够获得 10 也能够用 11 - 01 的方式,因此能够少作一次位运算
  • 第三步,统计两个 4个连续bit 中1的个数,例如 0011 0011 -> 0000 1100 ,而8个bit最多就8个1,用4个bit就能够表示了,因此能够先加,0011 1100,再消除,0000 1100,第四步第五步同理,前面的留着无论,最后返回的时候把前面的bit置0
  • return i & 0x3f,32位的int,最多就32个1,因此取后六位便可表示全部的可能

上述二者的使用

上面两个方法均在HashMap(JDK7实现)中的roundroundUpToPowerOf2方法中被调用,HashMap的分析详见http://www.javashuo.com/article/p-qcwimtzs-nv.html,仍是很是有意思的

相关文章
相关标签/搜索