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

参考链接

猜你喜欢

转载自juejin.im/post/5c3969b76fb9a049a5712060