为什么Jenkins一次一个散列函数中没有溢出检查


uint32_t jenkins_one_at_a_time_hash(const uint8_t* key, size_t length) {
size_t i = 0;
uint32_t hash = 0;
while (i != length) {
hash += key[i++];
hash += hash << 10;
hash ^= hash >> 6;
}
hash += hash << 3;
hash ^= hash >> 11;
hash += hash << 15;
return hash;
}

来源:https://en.wikipedia.org/wiki/Jenkins_hash_function

hash<<10添加到hash时,如果字符串长度较大,则可能会溢出。当使用多项式哈希函数对字符串进行哈希时,我们在每次迭代中取mod p值(puint32_t范围内的大素数(,并返回hash % hash_table_length。但是在这个算法中,我们为什么不使用hash = (hash + (hash<<10)) % p来防止溢出呢?我们如何确保返回的hash值保持在哈希表长度内?

一次一个散列的设计目的是将一个字节字符串作为输入,然后输出一个(大致(均匀分布的32位值。重要的是,这意味着,如果你想使用哈希函数将键分配到哈希表中,你需要执行一些次要的数学运算,将32位哈希转换为表索引,因为哈希函数本身并不是为了将其输出限制在较小数量的槽中。因此,例如,如果你想用它来分配密钥,你可以用表槽的数量来修改结果。

上一段中的推理基本上可以归结为"正如最初所写的那样,哈希函数不会试图放入少量插槽。"但这里还有一个更深层次的问题:为什么哈希函数没有为处理整数溢出做出任何规定,为什么它不在每一步都用素数来调整其内部值?这与指导散列函数的设计原则有关。一次一个散列在一篇关于使用可逆变换构建具有某些期望属性的散列函数族的文章中进行了描述。其想法是通过增量应用可逆变换来构建长字节流的哈希,该变换将现有哈希值和新字节组合在一起。这样做有一个有用的特性,即如果你对字节流X和Y进行了哈希处理,但没有发生冲突,那么如果你将相同的字节序列附加到X和Y上,就没有可能发生冲突。哈希中使用的特定操作确实是可逆的,但是关于为什么的理由是微妙的,并且依赖于这样一个事实,即移位相加并允许计算溢出对应于模232乘以可逆值。从这个意义上说,这里故意没有mod,因为溢出计算的隐式模执行"正确"的模,使所有步骤都可逆。

这与多项式散列模一些素数形成对比。你是对的,当使用多项式哈希策略时,你需要注意整数溢出,因为你特别不希望使用这些隐式mod-毕竟,你是在用不同的数字进行mod!但这对于特定的哈希策略来说是特别的。有很多不同的方法来构建哈希函数,每种方法都使用不同的方法。

最新更新