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,仍是很是有意思的

相關文章
相關標籤/搜索