看似简单的FNV1哈希实现会导致很多冲突

时间:2016-06-02 00:13:57

标签: java hash

我正在玩哈希表并使用大约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天真的方法)。

1 个答案:

答案 0 :(得分:0)

由于mod你的哈希函数的参数,我认为它是你想要哈希规范化的范围,即对于你期望它的特定用例{ {1}}。我假设这是因为:

  1. 该算法要求以2 n 模式进行计算,其中 n 是所需散列中的位数。
  2. 鉴于偏移基础 FNV Prime 是模块中的常量,并且等于64位散列的参数,{{1也应固定在2 64
  3. 由于不是,我认为它是理想的最终输出范围。
  4. 换句话说,给定一个固定的偏移基础和FNV Prime,没有理由传入mod参数 - 它由另外两个FNV参数决定。 如果以上所有都是正确的那么实现是错误的。您应该进行计算mod 2 64 并使用810,049应用最终余数运算。

    此外(但这可能并不重要),算法要求使用ASCII字符对较低的8位进行xoring,而使用16位进行xoring。我不确定这会有什么不同,因为对于ASCII,高阶字节无论如何都会为零,它的行为就像你只需要8位一样。