1. 程式人生 > >JDK原始碼閱讀-Integer.bitCount()

JDK原始碼閱讀-Integer.bitCount()

Q:統計二進位制數中bit位為1的個數

常規解法

思路:將二進位制的每一位依次與1作與運算,T=O(n),n為二進位制位數。

public int bitCount(int i) {
        int count = 0;
        do {
            if ((i & 1) == 1) {
                count++;
            }
            i >>= 1;
        } while (i > 0);

        return count;
    } 
複製程式碼

優化解法

思路:將整數減一後與原數作與運算,達到將原二進位制最低位"1"重置為"0"的目的。此時T=O(n)

,但n為二進位制中"1"的個數。

public int countBit(int i) {
        int count = 0;
        while (i > 0) {
            i = i & (i - 1); // 抹除二進位制中最低位的1
            count++;
        }
        
        return count;
    }
複製程式碼

java內建的Integer.bitCount()解法

思路:先每兩位一組統計二進位制中的"1",然後每四位一組統計"1",接著是8位、16位和32位,最終再與0x3f

作與運算,輸出結果。如下圖:

             二進位制                       十進位制
1  0  1  1  0  0  1  1  0  1  1  1    10 11 00 11 01 11
 01    10    00    10    01    10     1  2  0  2  1  2
    \ /         \ /         \ /        \ /   \ /   \ /
    0011        0010        0011        3     2     3
                    \       /           3      \   /
    0011               0101             3        5
        \             /                  \      /
              1000                          8
          
              2871的二進位制中的1的位數計算過程
複製程式碼

演算法原型:

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;
}
複製程式碼

其中16進位制數對應二進位制為:

16進位制 二進位制
0x55555555 01010101010101010101010101010101
0x33333333 00110011001100110011001100110011‬
0x0f0f0f0f 00001111000011110000111100001111‬
0x00ff00ff 00000000111111110000000011111111
0x0000ffff 00000000000000001111111111111111
0x3f 00111111‬

優化思路:

  1. 對於第一步:兩個bit計算1的數量:0b11: 0b01 + 0b01 = 0b10 = 2, 0b10: 0b00 + 0b01 = 0b01 = 1。研究發現:2=0b11-0b1,1=0b10-0b1,可以減少一次位於計算:i = i - ((i >>> 1) & 0x55555555)
  2. 對於第二步:無優化
  3. 對於第三步:實際是計算每個byte中的1的數量,最多8(0b1000)個,佔4bit,可以最後進行位與運算消位,減少一次&運算:i = (i + (i >>> 4)) & 0x0f0f0f0f
  4. 第四,五步:同上理由,可以最後消位。但是由於int最多32(0b100000)個1,所以這兩步可以不消位,最後一步把不需要的bit位抹除就可以了:i & 0x3f

優化原型演算法後,就得到java中內建的bitCount()方法:

public static int bitCount(int i) {
        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;
    }
複製程式碼

從數學角度推導

變數與表示式 公式 說明
i b_0*2^0+b_1*2^1+...+b_{31}*2^{31} b_i\epsilon[0,1] (2871)10, (101100110111)2
i >>> 1 b_1*2^0+b_2*2^1+...+b_{31}*2^{30} 無符號右移一位 (1435)10, (10110011011)2
(i >>> 1) & 0x55555555 (b_1*2^0+b_2*2^1+...+b_{31}*2^{30}) - (b_2*2^1+b_4*2^3+...+b_{30}*2^{29}) 將與運算轉換為減法 (1297)10, (10100010001)2
第一步:i - (i >>> 1) & 0x55555555 i=(b_0+b_1)*2^0+(b_2+b_3)*2^2+...+(b_{30}+b_{31})*2^{30} 實現每兩位一組統計"1"的個數,每組之間的公比是22,此時i的值已被更新 (1574)10, (1 10 00 10 01 10)2
第二步:(i & 0x33333333) + ((i >>> 2) & 0x33333333) i=(b_0+b_1+b_2+b_3)*2^0+(b_4+b_5+b_6+b_7)*2^4+...+(b_{28}+b_{29}+b_{30}+b_{31})*2^{28} 實現每四位一組統計"1"的個數,每組之間的公比是24,此時i的值已被更新 (803)10, (11 0010 0011)2
第三步:(i + (i >>> 4)) & 0x0f0f0f0f i=(b_0+b_1+...+b_7)*2^0+(b_8+b_9+...+b_{15})*2^8+...+(b_{24}+b_{25}+...+b_{24})*2^{28} 實現每八位一組統計"1"的個數,每組之間的公比是28,此時i的值已被更新 (773)10, (11 00000101)2
第四步:i + (i >>> 8) i=(b_0+b_1+...+b_{15})*2^0+(b_8+b_9+...+b_{23})*2^8+(b_{16}+b_{17}+...+b_{31})*2^{16}+(b_{24}+b_{25}+...+b_{31})*2^{24} 實現每16位一組統計"1"的個數,更新i的值 (776)10, (1100001000)2
第五步:i + (i >>> 16) i=(b_0+b_1+...+b_{31})*2^0+(b_8+b_9+...+b_{31})*2^8+(b_{16}+b_{17}+...+b_{31})*2^{16}+(b_{24}+b_{25}+...+b_{31})*2^{24} 實現所有32位中"1"的統計 (776)10, (1100001000)2
最後一步:i & 0x3f i=(b_0+b_1+...+b_{31})*2^0 因為int二進位制最多有25位"1",因此在第五步中因子大於25的後是三項需要被抹掉,只保留第一項,後者剛好是這個二進位制所有位數之和 (8)10, (1000)2

參考連結