假设你有两个哈希H(A)
和H(B)
,你想要将它们结合起来。我已经读过将两个哈希结合起来的好方法是XOR
,例如XOR( H(A), H(B) )
。
我发现的最好的解释在这里简要介绍了这些hash function guidelines:
XORing two numbers with roughly random distribution results in another number still with roughly random distribution*, but which now depends on the two values.
...
* At each bit of the two numbers to combine, a 0 is output if the two bits are equal, else a 1. In other words, in 50% of the combinations, a 1 will be output. So if the two input bits each have a roughly 50-50 chance of being 0 or 1, then so too will the output bit.
你能解释为什么XOR应该是组合散列函数(而不是OR或AND等)的默认操作的直觉和/或数学吗?
假设均匀随机(1位)输入,AND函数输出概率分布为75%0
和25%1
。相反,OR是25%0
和75%1
。
XOR函数是50%0
和50%1
,因此它有利于组合均匀概率分布。
通过写出真值表可以看出这一点:
a | b | a AND b
---+---+--------
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
a | b | a OR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 1
a | b | a XOR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
练习:两个1位输入a
和b
有多少逻辑函数具有这种统一的输出分布?为什么XOR最适合您问题中所述的目的?
xor是散列时使用的危险默认函数。它比和和更好,但是并没有多说。
xor是对称的,因此元素的顺序会丢失。所以"bad"
将哈希与"dab"
相同。
xor将相同的值映射为零,并且应避免将“common”值映射为零:
因此(a,a)
被映射到0,并且(b,b)
也被映射到0.因为这样的对比随机性更常见,所以你最终会在零时发生远远多于你应该的碰撞。
有了这两个问题,xor最终成为一个散列组合器,在表面看起来不太合适,但在进一步检查后却没有。
在现代硬件上,通常以与xor一样快的速度添加(它可能会使用更多的功率来实现这一点)。添加的真值表与所讨论的位上的xor类似,但当两个值均为1时,它还会向下一位发送一个位。这会擦除较少的信息。
所以hash(a) + hash(b)
更好,因为如果a==b
,结果是hash(a)<<1
而不是0。
这仍然是对称的。我们可以以适度的成本打破这种对称性:
hash(a)<<1 + hash(a) + hash(b)
又名hash(a)*3 + hash(b)
。 (计算hash(a)
一次,如果你使用移位解决方案,建议存储)。任何奇数常数而不是3
都会将size_t
(或k位无符号常数)双射地映射到自身,因为无符号常数上的映射是某些2^k
的数学模k
,而任何奇数常数都是2^k
的相对素数。
对于一个更加漂亮的版本,我们可以检查boost::hash_combine
,这是有效的:
size_t hash_combine( size_t lhs, size_t rhs ) {
lhs^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
return lhs;
}
在这里,我们将seed
的一些移位版本加上一个常数(基本上是随机的0
s和1
s - 特别是它是黄金比例的倒数作为32位定点分数),带有一些加法和xor。这打破了对称性,并且如果传入的散列值很差(例如,想象每个分量哈希值为0),则会引入一些“噪声” - 上面处理得很好,在每个组合后产生1
和0
s的涂片。我只是输出一个0
)。
对于那些不熟悉C / C ++的人来说,size_t
是一个无符号整数值,足以描述内存中任何对象的大小。在64位系统上,它通常是64位无符号整数。在32位系统上,32位无符号整数。
尽管它具有方便的位混合特性,但由于其可交换性,XOR不是一种结合哈希的好方法。考虑如果将{1,2,...,10}的排列存储在10元组的哈希表中会发生什么。
更好的选择是m * H(A) + H(B)
,其中m是一个很大的奇数。
图片来源:上面的合成器是Bob Jenkins的一个提示。
Xor可能是组合哈希的“默认”方式,但Greg Hewgill的答案也说明了它存在缺陷的原因:两个相同哈希值的xor为零。在现实生活中,有相同的哈希比人们预期的更为常见。然后,您可能会发现在这些(并非如此罕见)的极端情况下,生成的组合哈希值始终相同(零)。哈希碰撞会比你预期的要频繁得多。
在一个人为的例子中,您可能会将来自您管理的不同网站的用户的哈希密码组合在一起。不幸的是,大量用户重复使用他们的密码,并且产生的哈希值的惊人比例为零!
我希望明确指出找到此页面的其他人。 AND和OR限制输出,如BlueRaja - Danny Pflughoe试图指出,但可以更好地定义:
首先,我想定义两个简单的函数,我将用它来解释这个:Min()和Max()。
Min(A,B)将返回A和B之间较小的值,例如:Min(1,5)返回1。
Max(A,B)将返回A和B之间较大的值,例如:Max(1,5)返回5。
如果给你:C = A AND B
然后你可以找到C <= Min(A, B)
我们知道这一点,因为没有什么可以和A或B的0位使它们成为1。因此,每个零位保持为零位,并且每一位有机会变为零位(因此值更小)。
随着:C = A OR B
相反的是:C >= Max(A, B)
有了这个,我们看到了AND函数的推论。任何已经是一个的位都不能被OR成为零,所以它保持为1,但每个零位有机会成为一个,因此数字更大。
这意味着输入的状态对输出施加限制。如果你和任何一个90,你知道输出将等于或小于90,无论其他值是什么。
对于XOR,根据输入没有隐含的限制。在某些特殊情况下,您可以发现,如果您使用255对一个字节进行异或,则会得到相反的但是可以从中输出任何可能的字节。每个位都有机会根据另一个操作数中的相同位改变状态。
如果你使用偏置输入XOR
随机输入,输出是随机的。 AND
或OR
也是如此。例:
00101001 XOR 00000000 = 00101001 00101001 AND 00000000 = 00000000 00101001 OR 11111111 = 11111111
正如@Greg Hewgill所提到的,即使两个输入都是随机的,使用AND
或OR
也会导致偏差输出。
我们使用XOR
而不是更复杂的东西的原因是,嗯,没有必要:XOR
工作得很好,而且它非常愚蠢。
hashCode()
中各种版本的java.util.Arrays的源代码是一个很好的参考,用于实体,一般使用哈希算法。它们易于理解并翻译成其他编程语言。
粗略地说,大多数多属性hashCode()
实现遵循以下模式:
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
您可以搜索其他StackOverflow Q&As,以获取有关31
背后的魔力的更多信息,以及Java代码经常使用它的原因。它不完美,但具有非常好的一般性能特征。
覆盖左侧2列并尝试使用输出计算输入的内容。
a | b | a AND b
---+---+--------
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
当你看到1位时,你应该知道两个输入都是1。
现在对XOR做同样的事情
a | b | a XOR b
---+---+--------
0 | 0 | 0
0 | 1 | 1
1 | 0 | 1
1 | 1 | 0
XOR没有提供任何关于它的输入。