Integer.bitCount() 函式理解(儘量通俗易懂)
阿新 • • 發佈:2021-01-06
bitCount(int i) 函式,實現統計一個數的二進位制位有多少個 1 。如 5 的二進位制為 101,返回 2。
Jdk1.8 原始碼如下。初看一臉懵逼,再看還是一臉懵逼,分析 2 小時後,轟然開朗,遂有此文。
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;
}
基礎知識
- & 與操作:a & b, a 和 b 都為 1 結果為 1,否則結果為 0。可以用來取固定區間的值,比如 1001 1100,取前 4 位 1001 1100 & 1111 0000 = 1001,取後四位 1001 1100 & 0000 1111 = 1100。
- “>>>” 和 "<<< ":無符號右移和無符號左移。(涉及到負數如何表示,不在此文範疇)
- 程式碼中的十六進位制數的二進位制表示如下
原文 | 二進位制 |
---|---|
0x55555555 | 01010101 01010101 01010101 01010101 |
0x33333333 | 00110011 00110011 00110011 00110011 |
0x0f0f0f0f | 00001111 00001111 00001111 00001111 |
0x3f | 00000000 00000000 00000000 11111111 |
從結果入手
-
int 佔 4 個位元組,從左往右用 C1,C2,C3,C4 表示這 4個 位元組。入參 i 用 C1,C2,C3,C4 表示,i 經過一系列運算後,用 D1,D2,D3,D4 表示。
-
由返回值 i & 0x3f 可知,參與運算的只有 D4 並且 D4 的值就是結果值。為什麼只 D4 的值就是結果呢? 答案在最後兩行程式碼
i = i + (i >>> 8);// 第一行
i = i + (i >>> 16);// 第二行
- 最後兩行程式碼的邏輯如下圖:一個位元組 8 個 bit 位,右移 8 位就是移除最後一個位元組,同理右移 16 位就是移除最後兩個位元組。這 2 行程式碼執行完後,D4 的值其實是 D1 + D2 + D3 + D4,所以只取 D4 參與運算並返回就行。
分治思想
- 時間線回到最後兩行程式碼之前,D1,D2,D3,D4 代表著什麼?為什麼相加就是結果呢?
- 結論:D1 表示 C1 裡面有多少個 1 。同理 D2、D3、D4 表示 C2、C3、C4 分別有多少個 1 。所以 D1 + D2 + D3 + D4 能作為結果返回。
- 那麼 C1 如何轉到 D1 呢?,這就是前三行程式碼做的事
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
- 假設 C1 是 10110011。不妨換個角度看待 C1 每一位 bit 的意義:每一位 bit 表示這個 bit 位有多少個 1。 看起來好像是廢話,bit 位是 1 表示這一位有一個 1,bit 位是 0 表示這一位有零個 1 。第一行程式碼執行後,這種思想變得有意義了。用兩位是表示這兩位有多少個 1, 比如前兩位 10 可以用 01 表示這兩位有多少個 1,三四位 11 可以用 10 表示這兩位有多少個 1,以此類推。
- 第一行程式碼執行後 C1’ = 01 10 00 10,表示一二位有一個 1,三四位有兩個 1,五六位有零個 1 ,七八位有兩個 1。
- 第二行程式碼執行後 C1’’ = 0011 0010,表示前四位有三個 1,後四位有兩個 1。
- 第三行程式碼執行後 D1 = 00000101,十進位制是 5,可以直接作為子結果和 D2、D3、D4 相加並返回了。
執行流程如下:
前三行程式碼理解
- 最後兩行程式碼前面已經分析過。前三行程式碼的效果我們也已經知道了,但是如何達到上述效果還是一臉懵逼,換一個等價寫法方便理解,當然原始碼的效率要高。
原始碼:
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
等價寫法:
i = (i & 0x55555555) + ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f);
- 依然以 C1 = 10110011 為例,每一個 bit 代表一個區間,對應之前的廢話:bit 位是 1 代表這一位有一個 1,bit 位是 0 代表這一位有零個 1 。區間 1 至 8,方便描述把奇數位置叫「奇數區間」,偶數位置叫「偶數區間」。
- 第一行程式碼的加號前求得偶數區間的 1,加號後求得奇數區間的 1,相加等於合併了相鄰的「奇偶區間」,把C1的區間縮小為 4 個。
- 同理第二行程式碼把區間縮小為 2 個,第三行程式碼把區間縮小為 1 個。基於此,完成 C1 到 D1。
- 整個過程其實就是一個逆序的分治~
最後,上述流程可以在紙上畫一遍,一目瞭然。