1. 程式人生 > 其它 >Integer.bitCount() 函式理解(儘量通俗易懂)

Integer.bitCount() 函式理解(儘量通俗易懂)

技術標籤:jdk原始碼jdk

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。
  • “>>>” 和 "<<< ":無符號右移和無符號左移。(涉及到負數如何表示,不在此文範疇)
  • 程式碼中的十六進位制數的二進位制表示如下
原文二進位制
0x5555555501010101 01010101 01010101 01010101
0x3333333300110011 00110011 00110011 00110011
0x0f0f0f0f00001111 00001111 00001111 00001111
0x3f00000000 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。
  • 整個過程其實就是一個逆序的分治~

最後,上述流程可以在紙上畫一遍,一目瞭然。