为什么String中的equals方法不使用哈希?

时间:2013-01-10 16:20:19

标签: java string hashcode

String类中的equals方法的代码是

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            while (n-- != 0) {
                if (v1[i++] != v2[j++])
                    return false;
            }
            return true;
        }
    }
    return false;
}

我有一个问题 - 为什么这个方法不使用hashCode()?

据我所知,hashCode()可以快速比较两个字符串。

更新:我知道,两个不相等的字符串,可以有相同的哈希值。但两个相等的字符串具有相等的哈希值。因此,通过使用hashCode(),我们可以立即看到两个字符串不相等。

我只是认为在equals中使用hashCode()可以是一个很好的过滤器

更新2:这里有一些代码,我们在这里谈论。

这是一个String方法等于

的示例
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        if (hashCode() == anotherString.hashCode()){
            int n = count;
            if (n == anotherString.count) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = offset;
                int j = anotherString.offset;
                while (n-- != 0) {
                    if (v1[i++] != v2[j++])
                        return false;
                }
                return true;
            }
        }else{
            return false;
        }
    }
    return false;
}

8 个答案:

答案 0 :(得分:39)

Hashcode可能是对不平等的第一轮检查。但是,它提出了一些权衡。

  1. String哈希码是懒惰计算的,尽管它们使用“保护”值。如果你要比较长寿命的字符串(即,他们可能已经计算了哈希码),这不是问题。否则,您将无法计算哈希码(可能很昂贵)或者在尚未计算哈希码时忽略检查。如果你有很多短命的字符串,你会比你使用它更频繁地忽略支票。
  2. 在现实世界中,大多数字符串的前几个字符都不同,因此首先检查哈希码不会节省太多。当然,有例外(例如URL),但在真实世界编程中,它们很少发生。

答案 1 :(得分:14)

JDK的开发人员实际上已经考虑过这个问题。我在various messages中找不到它为什么没有被包括在内。此增强功能也列在the bug database

即,建议的更改之一是:

public boolean equals(Object anObject) {
    if (this == anObject) // 1st check identitiy
        return true;
    if (anObject instanceof String) { // 2nd check type
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) { // 3rd check lengths
            if (n != 0) { // 4th avoid loading registers from members if length == 0
                int h1 = hash, h2 = anotherString.hash;
                if (h1 != 0 && h2 != 0 && h1 != h2) // 5th check the hashes
                    return false;

还讨论了将==用于实习字符串(即,如果两个字符串都被实习:if (this != anotherString) return false;)。

答案 2 :(得分:7)

1)计算hashCode可能不会比直接比较字符串更快。

2)如果hashCode相等,则字符串可能仍不相等

答案 3 :(得分:4)

对于许多用例来说,这可能是一个好主意。

然而,作为一个广泛用于各种应用程序的基础类,作者真的不知道这个额外的检查是否可以平均保存或损害性能。

我猜测大多数String.equals()都是在Hashmap中调用的,之后已知哈希码相等,因此再次测试哈希码是毫无意义的。 / p>

如果我们考虑比较2个随机字符串,即使使用像US ASCII这样的小字符集,很可能哈希值不同,并且char-by-char比较在第1个char上失败。因此检查哈希值是浪费。

答案 4 :(得分:2)

AFAIK,可以在String中添加以下检查。这检查如果设置了哈希码并且它们不同,则字符串不能相等。

if (hash != 0 && anotherString.hash != 0 && hash != anotherString.hash)
    return false;
if (hash32 != 0 && anotherString.hash32 != 0 && hash32 != anotherString.hash32)
    return false;

答案 5 :(得分:0)

字符串哈希码不可免费且自动使用。为了依赖哈希码,必须为两个字符串计算它,然后才能进行比较。由于冲突是可能的,如果哈希码相等,则需要进行第二次char-by-char比较。

虽然String对于通常的程序员来说是不可变的,但是一旦计算出来,它确实有私有字段来存储它的哈希码。但是,只有在首次需要哈希码时才会计算此字段。正如您可以从字符串源代码here中看到的那样:

 private int hash;

 public int hashCode() {
      int h = hash;
      if (h == 0) {
         ...
         hash = h;  
      }
      return h;
 }

因此,首先计算哈希码是有意义的并不明显。对于你的具体情况(也许真正长字符串的相同实例很多次相互比较),它仍然可能是:profile。

答案 6 :(得分:0)

  

我认为,hashCode()可以更快地比较两个字符串。

参数?

反对此提案的论点:

  • 更多操作
来自hashcode()

String必须访问字符串中的每个字符,并且必须对每个字符进行2计算。
所以我们需要一个包含n个字符5*n操作的字符串(加载,乘法,查找/加载,乘法,存储)。两次,因为我们比较两个字符串。 (好吧,一个商店和一个负载在合理的实施中并不合适。)
对于最佳情况,这会对长度为10*xm以及n的两个字符串进行总计x=min(m,n)次操作。最坏的情况是10*x x=m=n。平均值介于两者之间,可能是(m*n)/2

在最佳情况3操作中,当前等于实现需求。 2次加载,1比较。对于长度为3*xm以及n的两个字符串,最差x=m=n次操作。平均值介于两者之间,可能是3*(m*n)/2

  • 即使我们缓存哈希值,也不清楚我们保存了什么

我们必须分析使用模式。可能在大多数情况下,我们只会问一次等于,而不是多次。即使我们多次询问,也不足以节省缓存时间。

不是直接反对速度,但仍然是好的反驳:

  • 反直觉

我们不希望哈希代码等于,因为我们肯定知道某些hash(a)==hash(b)的{​​{1}}。阅读本文的每个人(以及关于哈希的知识)都会想知道那里发生了什么。

  • 导致不良示例/意外行为

我已经可以看到关于SO的下一个问题了:“我有一个字符串有几十亿次'a'。为什么要将它与equal()与'b'进行比较需要永远?” :)

答案 7 :(得分:0)

如果哈希码考虑了字符串的全部内容,那么计算具有n个字符的字符串的哈希码需要n次操作。对于很长的字符串来说。比较两个字符串如果它们相同则需要n次操作,而不是计算散列。但如果字符串不同,那么很可能早就发现了差异。

字符串哈希函数通常不考虑非常长字符串的所有字符。在这种情况下,如果我比较两个字符串,我可以先比较哈希函数使用的字符,并且我至少和检查哈希值一样快。但是如果这些字符没有区别,那么哈希值将是相同的,所以我必须比较完整的字符串。

总结:一个好的字符串比较永远不会慢,但通常比比较哈希(并在哈希匹配时比较字符串)要快得多。