二进制算术:为什么hash%n等于hash&(n-1)?

时间:2017-09-08 08:26:41

标签: java hashmap bucket

我一直在研究Java HashMap源代码,它的一部分决定了放置一个对象的内容,并在Java 7(8)中看到了与Java 6相比的变化。 另外,我进行了大量的实验,两个表达式都有相同的结果:

hash % n
and
hash & (n - 1)
where n - the array length that must be power of 2.

我无法弄清楚为什么会这样?是否有任何定理或某些数学定律证明这些陈述是平等的?基本上我想理解推论并证明这两个陈述的等价性。

PS。如果n不是2数的幂,则等价性会立即中断。

3 个答案:

答案 0 :(得分:4)

如果n是2的幂,则表示其二进制表示为10000....
对于这个问题,n-1是1111111...,只有一个数字。

这意味着使用(n-1)的二进制& -ing仅保留k n-1设置的n = 8: 1000, n-1 = 7: 111中的位数。

示例k = 201: 11001001
& -ing例如k % n = k & (n-1) = 11001001 & 111 = 001 = 1
&

% - 使用2的幂意味着在二进制中你只需去掉上面(包括)唯一设置位的所有内容:对于n = 8,这意味着剥离所有内容(包括)第4位。而这正是& -ing所做的。

副作用是使用hash & (n - 1)是可交换的:(n - 1) & hash相当于%,这对namespace Essentia = essentia; namespace Essentia::Streaming = essentia::streaming; 来说是不正确的,许多地方的jdk源代码都使用了以后,例如in getNode

答案 1 :(得分:3)

考虑(n - 1)中的位如果n是2的幂(或((1 << i) - 1),如果您想简化n上的约束):

如果n是16(= 1 << 4),那么n - 1是15,1516的位代表(如32-位int s)是:

 1 = 00000000000000000000000000000001  // Shift by 4 to get...
16 = 00000000000000000000000000010000  // Subtract 1 to get...
15 = 00000000000000000000000000001111

所以只有最低的4位在15中设置。如果&使用另一个int,它只允许在结果中设置该数字的最后4位中的位,因此值将为只能在0-15范围内,所以它就像在做% 16

但请注意,这种等价不适用于负的第一个操作数:

    System.out.println(-1 % 2);    // -1
    System.out.println(-1 & (2-1));  //  1

Ideone demo

答案 2 :(得分:2)

整数/%的算术规则是:

x*(y/x) + (y%x) = y

负面hash -4和正面n 8

怎么样?
8*0 + (-4%8) = -4

因此modulo维持标志。

-4 % 8 = -4
-4 & 7 = 4

或者:

int t = hash%n;
if (t < 0) {
   t += n;
}
assert t == (hash & (n-1));

因此,早期的%n hash的java必须是积极的。 现在哈希可能是负面的,更稳固和更好的散列。 因此,这是java源代码中这种微妙变化的合理原因。

<强> 背景:

2 n 1 ,然后是n-1 0 (二进制)。 2 n - 1是n-1 1 s。

因此,n为2的正幂,并且为正数h:

h % n == h & (n-1)

另一种用法是计算int中的位数。 Integer类就是这样一个函数。

int bits = 0;
while (x != 0) {
    x &= x - 1;
    ++bits;
}