我正在玩哈希表并使用大约350,000个英语单词的语料库,我想尝试均匀分发。因此,我尝试将它们装入一个长度为810,049的数组(最接近的素数大于输入大小的两倍)。我很困惑地看到这样一个简单的FNV1实现:
public int getHash(String s, int mod) {
final BigInteger MOD = new BigInteger(Integer.toString(mod));
final BigInteger FNV_offset_basis = new BigInteger("14695981039346656037");
final BigInteger FNV_prime = new BigInteger("1099511628211");
BigInteger hash = new BigInteger(FNV_offset_basis.toString());
for (int i = 0; i < s.length(); i++) {
int charValue = s.charAt(i);
hash = hash.multiply(FNV_prime).mod(MOD);
hash = hash.xor(BigInteger.valueOf((int) charValue & 0xffff)).mod(MOD);
}
return hash.mod(MOD).intValue();
}
导致64,000次碰撞很多,基本上是20%的输入。我的实施有什么问题?这种方法是否存在缺陷?
编辑:除此之外,我还尝试并实现了其他散列算法,如sdbm和djb2,它们都执行相同的,同样糟糕。在这个语料库上都有这些〜65k的碰撞。当我将语料库更改为仅表示为字符串的350,000个整数时,会发生一些变化(例如,一个算法有20,000个冲突,另一个算法有40,000个),但碰撞的数量仍然非常高。为什么呢?
EDIT2:我刚刚测试了它,Java的内置.hashCode()导致同样多的冲突,即使你做了一些荒谬的天真,就像哈希是一个乘法模拟所有字符模数的产物810,049,它的表现只比所有那些臭名昭着的算法差一半(60k碰撞与90k天真的方法)。
答案 0 :(得分:0)
由于mod
是你的哈希函数的参数,我认为它是你想要哈希规范化的范围,即对于你期望它的特定用例{ {1}}。我假设这是因为:
换句话说,给定一个固定的偏移基础和FNV Prime,没有理由传入mod参数 - 它由另外两个FNV参数决定。 如果以上所有都是正确的那么实现是错误的。您应该进行计算mod 2 64 并使用810,049应用最终余数运算。
此外(但这可能并不重要),算法要求使用ASCII字符对较低的8位进行xoring,而使用16位进行xoring。我不确定这会有什么不同,因为对于ASCII,高阶字节无论如何都会为零,它的行为就像你只需要8位一样。