为什么XOR是组合哈希的默认方式?

问题描述 投票:130回答:8

假设你有两个哈希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等)的默认操作的直觉和/或数学吗?

cryptography bit-manipulation hash probability xor
8个回答
106
投票

假设均匀随机(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位输入ab有多少逻辑函数具有这种统一的输出分布?为什么XOR最适合您问题中所述的目的?


143
投票

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的一些移位版本加上一个常数(基本上是随机的0s和1s - 特别是它是黄金比例的倒数作为32位定点分数),带有一些加法和xor。这打破了对称性,并且如果传入的散列值很差(例如,想象每个分量哈希值为0),则会引入一些“噪声” - 上面处理得很好,在每个组合后产生10s的涂片。我只是输出一个0 )。

对于那些不熟悉C / C ++的人来说,size_t是一个无符号整数值,足以描述内存中任何对象的大小。在64位系统上,它通常是64位无符号整数。在32位系统上,32位无符号整数。


29
投票

尽管它具有方便的位混合特性,但由于其可交换性,XOR不是一种结合哈希的好方法。考虑如果将{1,2,...,10}的排列存储在10元组的哈希表中会发生什么。

更好的选择是m * H(A) + H(B),其中m是一个很大的奇数。

图片来源:上面的合成器是Bob Jenkins的一个提示。


16
投票

Xor可能是组合哈希的“默认”方式,但Greg Hewgill的答案也说明了它存在缺陷的原因:两个相同哈希值的xor为零。在现实生活中,有相同的哈希比人们预期的更为常见。然后,您可能会发现在这些(并非如此罕见)的极端情况下,生成的组合哈希值始终相同(零)。哈希碰撞会比你预期的要频繁得多。

在一个人为的例子中,您可能会将来自您管理的不同网站的用户的哈希密码组合在一起。不幸的是,大量用户重复使用他们的密码,并且产生的哈希值的惊人比例为零!


8
投票

我希望明确指出找到此页面的其他人。 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对一个字节进行异或,则会得到相反的但是可以从中输出任何可能的字节。每个位都有机会根据另一个操作数中的相同位改变状态。


2
投票

如果你使用偏置输入XOR随机输入,输出是随机的。 ANDOR也是如此。例:

00101001 XOR 00000000 = 00101001
00101001 AND 00000000 = 00000000
00101001 OR  11111111 = 11111111

正如@Greg Hewgill所提到的,即使两个输入都是随机的,使用ANDOR也会导致偏差输出。

我们使用XOR而不是更复杂的东西的原因是,嗯,没有必要:XOR工作得很好,而且它非常愚蠢。


0
投票

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代码经常使用它的原因。它不完美,但具有非常好的一般性能特征。


0
投票

覆盖左侧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没有提供任何关于它的输入。

© www.soinside.com 2019 - 2024. All rights reserved.