【问题标题】:Why the following three strings's hashcode are same?为什么以下三个字符串的哈希码相同?
【发布时间】:2018-11-05 13:35:31
【问题描述】:

看完JDK的源码,我还是很惊讶字符串 "AaAa", "AaBB" and "BBBB" 具有相同的哈希码。

JDK的源码如下,

int h = hash;
if (h == 0 && value.length > 0) {
    char val[] = value;

    for (int i = 0; i < value.length; i++) {
        h = 31 * h + val[i];
    }
    hash = h;
}
return h;

谁能澄清一下?

【问题讨论】:

  • 为什么这会让您感到惊讶?哈希码不是唯一的,相同的哈希码会有不同的字符串,而你恰好找到了三个。
  • "Aa""BB" 具有相同的哈希码。因此,相同长度的"Aa""BB" 序列将具有相同的哈希码。
  • @Jesper 有人可能会感到惊讶,为什么对努力避免冲突的加密哈希函数有经验。

标签: java string hashcode


【解决方案1】:

因为the hash code is defined to be calculated for a String

String 对象的哈希码计算为

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

所以:

  • 对于AaAa65*31^3 + 97*31^2 + 65*31 + 97 = 2031744
  • 对于AaBB65*31^3 + 97*31^2 + 66*31 + 66 = 2031744
  • 对于BBBB66*31^3 + 66*31^2 + 66*31 + 66 = 2031744

【讨论】:

  • 看起来很容易发生冲突?为什么选择这个哈希函数?
  • @AdamLee 因为它计算简单,平均而言效果很好。而且它是公共 API 的一部分,因此无法更改。
  • @AdamLee,因为让哈希冲突的发现变得困难曾经是也不是这个哈希函数的目标之一。
  • @AdamLee 预计会发生碰撞,但预计不会避免或避免。相等的哈希码并不表明对象相等,但只有它们可以相等。请参阅 Java hashcode/equals contract 以更好地了解其用途。
【解决方案2】:

因为probability

有大约 40 亿个可能的哈希码 (Integer.MIN_VALUE -&gt; Integer.MAX_VALUE) 和基本上无限可能的字符串。一定有collisions。事实上,the birthday problem 向我们展示了that only ~77,000 strings 是任意碰撞的高概率所必需的 - 如果哈希函数具有极高的熵,那么它不会。

也许您正在考虑cryptographic hash function,其中

对消息的小改动应该会改变哈希值,所以 广泛认为新的哈希值似乎与旧的不相关 哈希值

在这种情况下,Object.hashCode 不是为加密目的而设计的。

另见How secure is Java's hashCode()?

【讨论】:

  • 也许值得指出的是,由于birthday paradox,您更有可能仅与 77,400 个实例发生哈希冲突。
  • @AndyTurner 你是如何计算 77k 的?
  • 它在链接表中,在p = 0.50 列,第一行。我怎么知道它是 77.4k?不确定 - 这是我刚刚......“知道”的那些事实之一。我可能记错了:)
  • @AndyTurner 记得很好。几乎就在...(如果您想拍拍自己的背,请添加一个链接)
【解决方案3】:

它们的哈希码是

AaAa: ((65 * 31 + 97) * 31 + 65) * 31 + 97 = 2.031.744
AaBB: ((65 * 31 + 97) * 31 + 66) * 31 + 66 = 2.031.744
BBBB: ((66 * 31 + 66) * 31 + 66) * 31 + 66 = 2.031.744

数学就是这样,没什么好混淆的。
请注意 97 和 66 之间正好是 31 的差异,这就是使这些哈希码排列得如此出色的原因。

【讨论】:

    【解决方案4】:

    这里是Object#hashCode方法的Java文档描述:

    只要在 Java 应用程序的执行过程中对同一个对象多次调用它,hashCode 方法必须始终返回相同的整数,前提是在对象上的equals 比较中使用的信息没有被修改。这从一个应用程序的一次执行到同一应用程序的另一次执行,整数不需要保持一致。

    如果两个对象根据equals(Object) 方法相等,那么对两个对象中的每一个调用hashCode 方法必须产生相同的整数结果。

    如果根据java.lang.Object#equals(java.lang.Object) 方法,如果两个对象不相等,则不需要对这两个对象中的每一个调用hashCode 方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

    所以String类的实现也保持了上述特性。所以这是正常现象。

    【讨论】:

      【解决方案5】:

      有多种类型的哈希函数具有不同的设计和性能标准。

      1. 用于索引的哈希函数(例如关联数组和类似用法)可能会频繁发生冲突,因为哈希表代码会在某些命名器中处理这种情况,例如将它们放入列表或重新散列。在这里,一切都是关于及时的表现。 Java hash() 好像就是这种类型

      2. 另一种类型的函数,即 SHA* 等加密哈希,会以牺牲哈希性能为代价来努力避免冲突。

      3. 然而,第三种类型的哈希函数是密码验证器哈希,它被设计为非常慢(约 100 毫秒是常见的)并且可能需要大量内存并且不经常发生冲突不是问题。这里的重点是让蛮力攻击花费很长时间,直到不可行。

      Once 根据使用情况选择哈希的类型和特征。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-09-21
        • 2016-06-13
        • 1970-01-01
        • 2014-11-30
        • 1970-01-01
        • 2021-05-09
        • 2017-02-14
        相关资源
        最近更新 更多