我正在研究Java如何计算int的位集 我在脑海里想到了一些简单的东西(我认为是正确的):
public static int bitCount(int number){
final int MASK = 0x1;
int count = 0;
for(int i = 0; i < 32; i++){
if(((number >>> i) & MASK) == MASK){
count++;
}
}
return count;
}
相反,我发现了一种方法,我完全不知道在做什么(对我来说似乎很神奇):
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;
有人可以帮助理解这是什么以及为什么像我最初认为的那样简单的功能可能是坏的吗?
答案 0 :(得分:7)
不确定
i = i - ((i >>> 1) & 0x55555555);
5的位模式是0101
(4位),因此掩码是位模式01
的重复16次。该行计算该数字的每两位对中的位数。如果考虑两位对,(i >>> 1) & 0x1
将获得低位位置的高位。现在,有四种可能的两位模式
00 ~> 00 - 00 = 00
01 ~> 01 - 00 = 01
10 ~> 10 - 01 = 01
11 ~> 11 - 01 = 10
并且每个两位对现在包含原始位置的位数。
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
接下来,我们计算每个四位组(也称为半字节)中的位数。通过使用0x3 = 0011(b)
屏蔽半字节,我们得到半字节的低位两位的位数,(i >>> 2) & 0x3
从半字节的高位两位获得计数。现在添加了这些计数。由于每个计数最多为2,因此总和最多为4,因此不会留下半字节。
i = (i + (i >>> 4)) & 0x0f0f0f0f;
现在计算每个八位字节中的位数。每个半字节包含在该位置原始中设置的位数。向右移动四位将计数从每个位置的高阶半字节移动到低阶半字节,这些都被添加。然后我们还将计数从低阶半字节移动到相邻八位字节的高阶nets,由& 0x0f0f0f0f
掩盖。由于每个八位位组最多可以设置8位,因此计数不会离开八位位组的低位半字节。
i = i + (i >>> 8);
现在我们添加相邻八位字节对的计数。
i = i + (i >>> 16);
现在我们在高位两个八位位组和低位二位数中添加计数总数。
return i & 0x3f;
最后,除了最低阶八位字节之外的所有八位字节都被屏蔽掉,因为高阶八位字节仍然包含中间计数。
您的简单实现可能被认为是错误的原因是性能。复杂的bit-hack使用较少的操作而没有分支,因此速度更快。然而,这更容易出错。
计算int
(或long
)中设置位的另一种巧妙方法是
public static int bitCount(int n) {
int count = 0;
while(n != 0) {
++count;
n = n & (n-1);
}
return count;
}
这是有效的,因为n = n & (n-1)
清除n
中的最后一个设置位并保持其他所有内容不变。如果n
的位模式以
...100...0
n-1
的位模式是
...011...1
和n
中最后一位之前的位是相同的。在Java中,这保证也适用于负数,因为整数溢出具有环绕语义,因此如果n = Integer.MIN_VALUE
,位模式为100...0
且n - 1
变为Integer.MAX_VALUE
位模式011...1
。
答案 1 :(得分:3)
只有计算机具有有限的int值范围才能使用cool方法。 因为你需要在计算之前知道所有需要的掩码,所以它无法在无限范围(如BigInteger)中轻松工作(以及其他很酷的位算法)。
无论如何,你可以通过以下方式阅读它的工作原理: http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
它位于本章的底部。