为什么不为字符串散列表只是使用这种无碰撞功能? (包括在下面)

时间:2013-08-03 23:53:43

标签: java hash hashcode

所以我有一个无碰撞的哈希函数(一个非常简单的函数),我想知道为什么没有使用像这样的无碰撞哈希函数。我猜的原因一定是它占用了太多的空间或者什么,但我想知道真正的答案。

这是功能:

如果你有一个单词w由n + 1个字符组成ß n ß n-1 ...ß 1 ß 0 ,然后定义哈希函数

H(w)= 26 n n + 26 n-1 n-1 + ... + 26 *ß 1 0

其中,例如,a = 1,b = 2,c = 3,...,z = 26.

此函数没有冲突,因为它定义了String和整数之间的一对一映射。

问题当然是随着单词长度的增加,哈希码变得非常大。

一个可能的解决方案是:拆分长字并使每个哈希码成为一个向量,第二个元素指向单词的其余部分(如果它被分割,它又可以指向该单词的另一部分)不止一次)。

所以我的问题是:为什么这没有实现?记忆的额外成本是否值得避免碰撞?这种方法是否因其他原因而变得很差? 我是第一个想到这样做的人吗?(开玩笑说最后一个。)

4 个答案:

答案 0 :(得分:9)

  

所以我的问题是:必须有一个理由说明为什么没有实施?

这样的问题只能由设计/实施API的人明确回答。

但是,我可以想到很多原因:

  1. 完美的哈希函数是不切实际的。对于长度小于相对较小数字的字符串,多项式会导致32位整数运算溢出。当发生这种情况时,该功能将不再完美。

  2. 即使在它提供完美哈希的空间子集中,值的扩展也足够大,以至于该功能仍然不切实际。创建一个基本数组具有2^31元素的哈希表是很难的。如果你不这样做,当完美的哈希值减少(%)到哈希数组的大小时,你就会发生冲突。

  3. 您的函数假定字符串仅由字母(在一种情况下)组成。您需要将26更改为96以仅支持ASCII的可打印子集。对于真正的Java字符串,它需要是65536 ...并且您的哈希函数只适用于2个字符串!

  4. 即使您可以解决上述问题(即对一小组字符串使用实用完美哈希函数),也存在具有完美哈希键的Map类型的问题实用性非常有限。实际上有限(AFAIK),Guava,Apache Commons,Trove或Fastutils库都没有专家Map类型,它使用完美的字符串哈希函数。 (有Map(或Map - 类)实现允许使用外部哈希函数......但这不是你所说的。)

    为了记录,当人们谈论完美的哈希函数时,他们通常使用单词 minimal ;即最小完美散列函数


    <强>更新

    (警告:这与原始问题相关。只有在您感兴趣的情况下才能阅读。)

    Supercat评论如下:

      

    还值得注意的是,存在一些代码,遗憾的是它依赖于字符串哈希函数的确切行为。

    那只是&#34;不幸&#34;如果您认为以下是行为定义方式的问题。

      

    如果不是这样,可能需要修复一些更严重的问题,例如重复调用hashCode为零的字符串将比重复调用具有非零哈希码的字符串花费更长的时间。这个问题可以通过if(hash == 0)hash = length来便宜地修复; (因为散列和长度很可能在那个时候在寄存器中,执行时间应该是最小的。)

    这假设我们接受零哈希码案例是一个严重的问题。我告诉你,这根本不是一个严重的问题。

    • 如果我们假设我们的字符串是随机创建的,那么任何给定字符串具有零哈希码的概率是2 32 中的一个。这是一个非常小的数字......

    • 如果我们确实得到零哈希码,那么成本就是我们每次调用hashcode()时重新计算哈希码。但成本并不是那么好。

    在典型情况下,在哈希表中使用String时使用hashcode()方法。让我们假设我们正在讨论密钥是String的情况,并且我们正在使用HashMapHashSet类与标准(OpenJDK 6/7)实现。< / p>

    • 如果只使用一次String来探测哈希表,则其hashcode将被计算一次,无论其值如何。

    • 如果将一个String作为键合并到哈希表中,则其hashcode将被计算一次......因为HashMapHashSet会在条目中缓存哈希码。 (换句话说,String中缓存的哈希码值与此用例无关......

    • 如果实施该应用程序以执行类似&#34;探测,则添加&#34;或者&#34;探测然后删除&#34;,并且用于探测的字符串键的哈希码为零,然后你进行两次而不是一次计算。

    • 唯一存在重大性能问题的情况是,重复使用相同的String对象作为键来探测哈希表...并且该键的哈希码为零。

    我认为如果一个应用程序使用相同的密钥重复探测,那么明智的做法是修复那个,而不是担心在40亿的情况下,哈希码为零

    但我假设我们正在谈论&#34;随机&#34;字符串。如果我们处理故意选择具有零哈希码的字符串以解决性能问题......或者由此产生的其他问题。

    让我们再看看上面的分析。四颗子弹中有三颗说没有问题。只有应用程序反复探测的情况才有问题。因此,对问题的简单缓解是设计应用程序,以便重复使用相同的String对象进行探测并不需要发生。

    (并且让我们退一步。如果有人试图使用String键导致性能问题,有更好的方法可以做到这一点。例如,如果他们知道平台上使用了什么算法,他们可以选择一组长度M的字符串&#34;几乎相等&#34;并且所有字符串都散列到相同的散列值。然后安排将这些键的N作为键添加到HashMap中。现在,具有相同属性的另一个键的探测将导致至少N字符串比较,从而导致O(N*M)字符比较。这可能是性能更差,并且更难以通过应用程序编程来缓解。)

    最后,即使我们接受这是一个需要通过更改hashcode方法进行修复的问题,还有另一种方法可以不涉及更改String规范。向boolean对象添加额外的私有String字段,以便hashcode == 0没有超载的含义! (当然,它会使String更大......但如果重载是一个重要的问题,那不重要。)

答案 1 :(得分:4)

哈希点是将结果快速映射到数组索引。如果你的哈希是任意大的,你就已经打败了哈希的目的。

答案 2 :(得分:1)

HashCode只是HashMap,HashTable和类似结构的辅助字段。

它不一定是非碰撞,它只用于加速排序过程和查找。

拥有完美而复杂的算法并非必要,如果它过于复杂,它只会减慢过程。更不用说为了这个目的,一些巨大的数字并不完全可行。

详细解释了wikipedia page

答案 3 :(得分:1)

实践中有限制。您的方法无法提供合理的

  • 哈希的计算时间
  • 内存要求

保证每个可能元素都是唯一的完美哈希不能丢弃任何信息。它可能只会改变它们。对于String,您只需使用

即可
BigInteger hash = new BigInteger(string.getBytes());

对兆字节哈希数据的计算将不再快速,您基本上是通过.equals比较每个对象,而意图是通过哈希进行比较要快得多,因为它不会比较每一位信息。这意味着哈希映射需要冲突。

您仍然应该使用每一点信息来计算哈希值。如果你不这样做,你最终可能会少于你的哈希值或不均匀分布的数字空间,其中一些哈希值是不成比例的大量输入值的结果。

元素的相似性不应该表示哈希值的相似性。在大多数实现中,您可以改进这一点,但这通常意味着您需要增加计算时间。

HashMaps在实践中工作得非常好,以至于额外的计算时间没有任何好的效果。

相关问题