Java 字符串上 hashCode()的一致性

Java String 的 hashCode 值计算为(HashCode ()) :

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

在任何情况下(比如 JVM 版本、供应商等) ,下面的表达式的计算结果是否为 false?

boolean expression = "This is a Java string".hashCode() == 586653468

更新 # 1: 如果你声称答案是“是的,有这样的情况”——那么请给出一个具体的例子,说明什么时候“这是一个 Java 字符串”。HashCode () != 586653468.尽量说得具体一点。

更新 # 2: 我们都知道,依赖 hashCode ()的实现细节通常是不好的。但是,我要特别指出的是 String.hashCode ()——所以请将答案集中在 String.hashCode ()上。HashCode ()在这个问题的上下文中是完全不相关的。

123082 次浏览

您不应该依赖于等于特定值的哈希代码。只是它将在同一个执行中返回一致的结果。 API 文件如下:

HashCode 的总合同是:

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

剪辑 由于 String.hashCode ()的 javadoc 指定了如何计算 String 的 hash 代码,因此任何违反这一规范的行为都将违反公共 API 规范。

再来一杯需要担心的问题是 Java 的早期版本和晚期版本之间的实现可能发生变化。我不认为实现细节是一成不变的,因此升级到 未来 Java 版本可能会导致问题。

底线是,我不会依赖于 hashCode()的实现。

也许您可以通过使用这种机制突出显示您实际试图解决的问题,这将突出显示一种更合适的方法。

如上所述,通常不应该依赖于保持不变的类的哈希代码。请注意,甚至 同一个虚拟机上随后运行的 同样的申请也可能产生不同的散列值。AFAIK 的 Sun JVM 的散列函数在每次运行时计算相同的散列,但这并不能保证。

请注意,这不是理论上的。Java.lang 的 hash 函数。JDK1.2中的字符串 改变了(旧的散列在 URL 或文件名等分层字符串方面存在问题,因为它倾向于为只在末尾不同的字符串生成相同的散列)。

Java.lang.String 是一个特例,因为(现在)已经记录了它的 hashCode ()算法,所以您可以依赖它。我还是觉得这是个坏习惯。如果您需要一个具有特殊的、有文档记录的属性的 hash 算法,只需要编写一个: ——)。

我可以看到早在 Java 1.2时期的文档。

虽然 一般来说的确不应该依赖于保持不变的哈希代码实现,但是现在它是 java.lang.String的文档化行为,因此更改它将被视为破坏现有的契约。

只要有可能,你不应该依赖于哈希代码在不同版本之间保持一致等-但在我看来,java.lang.String是一个特例,因为算法 已经已经被指定... 当然,只要你愿意放弃与发布版本的兼容性,在算法被指定之前。

只是为了回答你的问题,而不是继续任何讨论。Apache Harmony JDK 实现似乎使用了不同的算法,至少看起来完全不同:

Sun JDK

public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;


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

阿帕奇和谐

public int hashCode() {
if (hashCode == 0) {
int hash = 0, multiplier = 1;
for (int i = offset + count - 1; i >= offset; i--) {
hash += value[i] * multiplier;
int shifted = multiplier << 5;
multiplier = shifted - multiplier;
}
hashCode = hash;
}
return hashCode;
}

你可以自己检查。

我发现了一些关于 JDK 1.0和1.1以及 > = 1.2的东西:

在 JDK 1.0.x 和1.1. x 中,hashCode 长字符串的函数 抽取每个 n 个字符。这个 我保证你会的 许多字符串散列到同一个字符串 值,从而减慢了 Hashtable 的速度 在 JDK 1.2中,该函数具有 被改进以使结果成倍增加 到目前为止31,然后加上下一个 字符的顺序。这是一个 慢一点,但是在 避免碰撞。 来源: 《 http://mindprod.com/jgloss/hashcode.html 》

一些不同的东西,因为你似乎需要一个数字: 如何使用 CRC32或 MD5而不是散列码,你是好去-没有讨论,没有担心在所有..。

如果您担心更改和可能不兼容的 VM,只需将现有的哈希代码实现复制到您自己的实用程序类中,并使用它来生成您的哈希代码。

散列码将根据 String 中字符的 ASCII 值计算。

这是 String 类中的实现,如下所示

public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}

散列码中的冲突是不可避免的。例如,字符串“ Ea”和“ FB”给出的散列码与2236相同