在Java中比较字符串的最快方法是什么?

31

在Java中,最快速的比较两个字符串的方法是什么?

是否有比equals方法更快的方法?

编辑: 我无法帮助澄清问题。

我有两个按字母顺序排序并且大小完全相同的字符串

例如:abbcee和abcdee

字符串可以长达30个字符。


12
为什么equals()方法对你来说会很慢? - BoltClock
6
你是否对你的应用程序进行了性能分析,而且结论是你代码中的热点问题是由于String.equals(...)引起的?如果你还没有对你的应用程序进行性能分析,那么为什么认为String.equals(...)可能会是一个问题? - Bart Kiers
4
他的问题并没有说equals()函数很慢,只是想知道有没有比equals()更快的方法。 - Sagar
2
他的问题确实表明equals方法很慢(或者至少不够快),因为他说“或者比equals更快的方法”。 - KevinDTimm
1
同意 - 就目前而言,这是一个糟糕的问题。如果你想要比equals()更快的东西,那么要么你有一些非常具体的性能要求,并且有相应的测量数据(在这种情况下,在提供任何适当的答案之前必须发布这些数据),要么你实际上并没有(不寻常的性能要求),在这种情况下,你应该只使用equals()。暗示“equals不够快”而没有任何理由,让人们无从下手。 - Andrzej Doyle
显示剩余7条评论
7个回答

36

我并不认为Sun Oracle没有将标准的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;
}

1
这看起来已经很优化了...从理论上讲,可以进一步针对OP的特定约束进行优化(例如使用字符串已经具有相等长度的知识,并且字符串中间存在不同字符的可能性更高),但是显然你无法在实践中这样做,因为该类是final的,而字段是private的...为挖掘出源代码点赞! - mikera
我不明白为什么他们在进行整个字符串比较之前没有先比较哈希码。这样会更快。 - Stephan
10
@Stephan:那样会更加低效。hashCode() 循环遍历字符串的所有字符来执行计算。如果 hashCode() 最终不相同,那么 equals() 基本上需要再次循环遍历所有字符。 - BalusC
6
这只是真相的一部分。一旦计算完成,hashCode()方法将哈希码存储为int类型,所以下次比较会非常快。 - Stephan
2
请注意,String :: equals和许多其他String方法是内置函数,由编译器替换为特定架构的预先烘焙的汇编代码块。在内置函数到位之前,Java代码才相关,因此几乎不会发生变化。 - Nitsan Wakart
显示剩余5条评论

28

使用哈希码快速比较相同长度的字符串:

public static boolean equals(final String s1, final String s2) {
return s1 != null && s2 != null && s1.hashCode() == s2.hashCode()
    && s1.equals(s2);
}

您可以进行测试,我的结果包括4000000个比较操作,其中包括相同的、相等的和不同的字符串:

String.equals(String):  177081939
equals(String, String):  44153608

注意: 计算新字符串对象的hashCode需要一些计算时间,然后将hashCode存储在对象中。因此,我的建议只有在重用字符串对象时才比默认的比较方法更快。在我的应用程序中,我使用String常量并将字符串存储在集合中。使用我的方法进行多次字符串比较实际上对我来说更快,但这可能不是普遍情况。

如果该方法一直用于新字符串,例如compare("a", "b"),那么它不会有所改善。

因此,比较字符串的最快方式取决于以下因素:

  • 您的字符串对象是否被重用(例如来自集合)或者总是新的(例如来自输入流)
  • 你的字符串是否具有不同的长度
  • 你的字符串是否在字符串的开头或结尾位置不同
  • 你的编程风格,使用了多少常量
  • 你对于String.intern()方法的使用

忽略这些事实,大多数程序都可以使用String.equals()方法。


+1 我一直在使用这个工具进行大量的“文字处理”,性能非常出色。 - xchiltonx
9
值得一提的是,哈希码可能存在冲突,因此比较哈希值可能会出现非常罕见的误判。这也解释了为什么你仍然需要使用equals。因此,如果大多数字符串都相等,这种方法可能会更慢。 - Nepoxx
1
你为什么要添加“一些长度” - Flow
2
这样怎么更快呢?你仍然使用s1.equals(s2) - vedi0boy
@SumitKumarSaha 感谢您提供的链接,您部分正确,因此我更新了我的答案。我希望人们在知道自己在做什么的情况下使用这样的优化。真正需要这样调整的程序将读取、计算和写入数据。我假设在计算过程中,字符串数据存储在集合中,并与常量或彼此进行比较,因此存在一种优化。 - Stephan
显示剩余2条评论

5

我曾尝试过不同的字符串比较组合(代码在这里):

1. s1.equals(s2)
2. s1.length() == s2.length() && s1.hashCode() == s2.hashCode() && s1.equals(s2)
3. s1.hashCode() == s2.hashCode() && s1.equals(s2);
4. s1.length() == s2.length() && s1.equals(s2);

我使用了长度为40的字符串,在进行10000000000次迭代之前,我重新初始化了这些字符串。

对于相等的字符串,我的结果是:

equal: 2873 milis ???
equal: 21386 milis
equal: 7181 milis
equal: 2710 milis ???

对于长度相同但最后一个字符不同的字符串:

different: 3011 milis
different: 23415 milis
different: 6924 milis
different: 2791 milis

对于不同的大小,s2中几乎相同的字符串末尾添加了一个字符:

different size: 3167 milis
different size: 5188 milis
different size: 6902 milis
different size: 2951 milis

在使用equals()方法之前,最好先使用string.length()进行比较。

但是,这几乎没有影响,因为我需要进行10^10次字符串比较,每个字符串有40个字符长度,对我来说奇怪的是,对于相等的字符串,当我先比较字符串长度时,速度更快。


7
我认为你的数据有问题。当比较相同长度的字符串时,算法4(先比较长度再使用.equals())怎么可能比算法1(仅使用.equals()进行比较)更快呢?对于这些情况,算法4执行了一个不必要的字符串长度比较,这将始终返回true。 - Tyler

4

如果你能证明它是一个重要的瓶颈,这会让我感到惊讶,但你可以尝试

s1.hashCode() == s2.hashCode() && s1.equals(s2)

它可能会更快。也可能不会。


这也是我的第一个想法。由于字符串是不可变的(这个拼写真的正确吗?),因此您基本上在比较常量整数,这应该很快。只有当对象大部分时间相等时才可能出现问题,然后您可以动态交换实现。太遗憾了,我没有jdk在这台机器上,现在很想对其进行分析。 - atamanroman
1
是的,它更快。但是在使用之前需要进行“null”检查。 - Stephan
1
我认为除非你以某种方式缓存哈希码,否则这不会更快。我认为equals比计算哈希码更快。 - jontro
两个字符串在计算它们的哈希码之前应该先进行哈希计算(仅当字符串被用作哈希表中的键时才会发生)。同时,0 哈希仍然是有效值。 - Sergey Ponomarev
@jontro String的哈希码并不是在每次调用时计算的。由于String是不变的,它们可以在内部预先计算或缓存,并且实际上也是这样做的。 - user207421

3

这取决于您的需求。我认为equals()非常优化,但也许您需要比equals()更快的东西。请看这篇文章


1
简单回答。

String.equals(Object)

我非常确定(这个答案有一些参考),很可能JIT会对String#equals进行内置处理,这意味着它能够替换调用,使用特别定制的机器代码来适配当前运行在你的JVM体系结构上。


0

一如既往,您需要为您的应用程序/环境进行基准测试。除非您已经对其进行了分析并确定其为性能瓶颈,否则这可能并不重要(“过早优化是万恶之源”)。

话虽如此:

a.equals(b) 对于字符串来说是非常快速的。这可能是Java平台中最紧密优化的代码之一。如果您能找到任何更快的比较两个任意字符串的方法,我会非常惊讶。

特殊情况可以安全地使用(a==b)进行欺骗,例如,如果您知道{{link1:两个字符串都被interned}}(因此值标识意味着对象标识)。在这种情况下,它可能比a.equals(b)稍微快一些-但这又取决于编译器/JVM实现。如果您不知道自己在做什么,很容易自食其果.....


我刚刚进行了微基准测试,结果显示在我的环境中(Sun Java 1.6上的Eclipse),(a==b)比a.equals(b)快2-4倍(30ns vs. 70-110ns)。你的情况可能会有所不同,当然也要注意微基准测试的常规警告。 - mikera
看了 @BalusC 发布的实现代码,我完全看不到任何重大优化,没有什么可以证明你的说法。当然,优化这个已经很简单的代码并不容易。但是从低级别来看,可以采用char-wise到int-wise比较的方式进行优化(显然,这需要Java中不容易获得的低级技巧,并且可能并不更快)。 - Konrad Rudolph
嗯,对我来说它看起来非常紧密地进行了优化,例如他们正在将字符串长度重复使用作为负循环计数器(这是一种经典的低级优化)。个人而言,我无法看到任何额外的优化措施,除非放弃纯Java并转向专门的本地实现(而且JIT可能已经这样做了...)。 - mikera
你可以安全地使用 equals() 和 intern 字符串。equals() 方法确实会检查身份。 - Stephan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接