Integer中bitCount方法的源码解析
文章目录
Integer 中的 bitCount() 方法用于统计二进制数字中 bit 为 1 的个数,例如 0b111
中 bit 为 1 的个数有 3 位。
源码为:
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;
}
对于统计二进制数字中 bit 为 1 的个数,我们首先可能会想到使用遍历二进制数字上的所有位数来求和。最多需要循环 32 次。
事实上,统计二进制数字中 bit 为 1 的个数,我们一般使用汉明码来计算,过程如下:
- 先统计每两位 bit 中 1 的个数,方法是
(i & 0x55555555) + ((i >>> 1) & 0x55555555)
,0x55555555
对应的二进制数字为01010101010101010101010101010101
,(i & 0x55555555)
能得到相邻两位 bit 中低位中 1 的个数,((i >>> 1) & 0x55555555)
能得到相邻两位 bit 中高位中 1 的个数,相加便为每两位 bit 中 1 的个数。 - 再统计每 4 位 bit 中 1 的个数。
- 再统计每 8 位 bit 中 1 的个数。
- 再统计每 16 位 bit 中 1 的个数,得到最后结果。
这是一种分而治之的算法,代码如下:
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 & 0b111111;
对应的二进制代码为:
i = (i & 01010101010101010101010101010101) + ((i >>> 1) & 01010101010101010101010101010101);
i = (i & 00110011001100110011001100110011) + ((i >>> 2) & 00110011001100110011001100110011);
i = (i & 00001111000011110000111100001111) + ((i >>> 4) & 00001111000011110000111100001111);
i = (i & 00000000111111110000000011111111) + ((i >>> 8) & 00000000111111110000000011111111);
i = (i & 0b00000000000000001111111111111111) + ((i >>> 16) & 0b00000000000000001111111111111111);
return i & 0b111111;
但是这代码明显跟源码不一样,因为源码做了优化。
在第一行代码中,每两位 bit 位共有 3 种情况。
(0b10 & 0x55555555) + ((0b10 >>> 1) & 0x55555555) = 0b00 + 0b01 = 0b01
,同时0b10-0b01=0b01
也成立(0b01 & 0x55555555) + ((0b01 >>> 1) & 0x55555555) = 0b01 + 0b00 = 0b01
,同时0b01-0b00=0b01
也成立(0b11 & 0x55555555) + ((0b11 >>> 1) & 0x55555555) = 0b01 + 0b01 = 0b10
,同时0b11-0b01=0b10
也成立
能看出来,(i & 0x55555555) + ((i >>> 1) & 0x55555555)
等同于 i - ((i >>> 1) & 0x55555555)
,而使用 i - ((i >>> 1) & 0x55555555)
能减少一次移位操作。
再看第三行代码中,因为 8 位 bit 最多存在 8 个 1,对应的二进制数字为 0b1000
,可以使用 4 位 bit 位存起来,所以 i
可以直接和 ((i >>> 2) & 0x33333333)
相加,将结果存储在原来的 4 位 bit 位上。
再看第二行代码,因为 4 位 bit 位最多存在 4 个 1,对应的二进制数字为 0b0100
,超出了 2 位 bit 可以存的位数,所以需要保持原来的移位。
第四,第五行也同理,因为 int 只有 32 位,对应二进制数字为 0b100000
,可以使用 5 位 bit 存起来,未超出 8 位,所以直接移位后相加。
最后第六行,只保留低 5 位 bit 作为结果。
文章作者 梧桐碎梦
上次更新 2023-12-10 21:11:35