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 |
优化思路:
- 对于第一步:两个bit计算1的数量:
0b11: 0b01 + 0b01 = 0b10 = 2, 0b10: 0b00 + 0b01 = 0b01 = 1
。研究发现:2=0b11-0b1,1=0b10-0b1
,可以减少一次位于计算:i = i - ((i >>> 1) & 0x55555555)
- 对于第二步:无优化
- 对于第三步:实际是计算每个byte中的1的数量,最多8(0b1000)个,占4bit,可以最后进行位与运算消位,减少一次&运算:
i = (i + (i >>> 4)) & 0x0f0f0f0f
- 第四,五步:同上理由,可以最后消位。但是由于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 |
![]() |
![]() |
(2871)10, (101100110111)2 |
i >>> 1 |
![]() |
无符号右移一位 | (1435)10, (10110011011)2 |
(i >>> 1) & 0x55555555 |
![]() |
将与运算转换为减法 | (1297)10, (10100010001)2 |
第一步:i - (i >>> 1) & 0x55555555 |
![]() |
实现每两位一组统计"1"的个数,每组之间的公比是22,此时i的值已被更新 | (1574)10, (1 10 00 10 01 10)2 |
第二步:(i & 0x33333333) + ((i >>> 2) & 0x33333333) |
![]() |
实现每四位一组统计"1"的个数,每组之间的公比是24,此时i的值已被更新 | (803)10, (11 0010 0011)2 |
第三步:(i + (i >>> 4)) & 0x0f0f0f0f |
![]() |
实现每八位一组统计"1"的个数,每组之间的公比是28,此时i的值已被更新 | (773)10, (11 00000101)2 |
第四步:i + (i >>> 8) |
![]() |
实现每16位一组统计"1"的个数,更新i的值 | (776)10, (1100001000)2 |
第五步:i + (i >>> 16) |
![]() |
实现所有32位中"1"的统计 | (776)10, (1100001000)2 |
最后一步:i & 0x3f |
![]() |
因为int二进制最多有25位"1",因此在第五步中因子大于25的后是三项需要被抹掉,只保留第一项,后者刚好是这个二进制所有位数之和 | (8)10, (1000)2 |
参考链接
转载自:https://juejin.cn/post/6844903760045539341