JDK原始碼閱讀-Integer.bitCount()
阿新 • • 發佈:2019-01-12
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)
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 |