【问题标题】:Why doesn't String.equals check for equality of char[] value?为什么 String.equals 不检查 char[] 值的相等性?
【发布时间】:2013-09-18 20:00:14
【问题描述】:

我正在查看java.lang.String 的源代码,并注意到equals method 不检查支持每个字符串的char[] 是否是同一个对象。这不会缩短比较时间吗?

此重写版本中包含的假设改进:

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;
               /** Begin Optimization **/
               if(v1==v2 && i==j){
                   return true;
               }
               /** End Optimization **/
               while (n-- != 0) {
                   if (v1[i++] != v2[j++])
                       return false;
               }
               return true;
           }
       }
       return false;
   }

我相信这会在两个字符串是使用String.substring 获得的情况下提高性能,甚至可能是实习字符串。

有人知道他们选择不以这种方式实施它是否有原因?

更新: 对于可能不太了解implementation of String 的任何人,除了字符串池之外,还有一些情况下,两个字符串对象可以具有相同的 char[] 值、int 偏移量、和 int 计数。

考虑以下代码:

String x = "I am a String, yo!";
String y = x.split(" ")[3];
String z = x.substring(7,14);

你最终会遇到这样的情况:

显然,字符串的值共享功能在 Java 7u6 中已被取消,以满足某些基准。因此,如果您通过使用 String.substring() 而不是字符串连接花时间让代码在适当的时间(或完全)运行,那么您就是 SOL。

【问题讨论】:

  • 方法是比较每个字符。
  • 可能没有意义,因为总是使用clone()数组
  • 它可能在 Java 7 之前的极少数情况下提高了性能——例如foo.substring(i, j).equals(foo.substring(i, j))——但当字符串数组 not 相等时,它也会对更常见的情况进行额外检查,这似乎平均花费的时间可能比节省的时间多。参见例如this blog post.
  • @SotiriosDelimanolis 我不是在问他们是否是 .equal();我在问它们是否是同一个对象。
  • @LouisWasserman 我会说这个优化和他的例子之间最重要的区别是,这将通过再运行 O(1) 行代码(1 或 2 次比较)来防止 O(n) 操作,取决于我们有多幸运);而他的示例仅阻止了 n=0 或 n=1(基本上)的 O(n) 操作。

标签: java string performance micro-optimization


【解决方案1】:

好吧,您需要检查char[]offset count(字符串长度)。由于char[] 仅在String 类中创建,因此所有三个相等的唯一方法是String 从自身创建一个分身。你可以让它做到这一点(例如new String("why?")),但这不是一个常见的用例。

<speculative>我什至不确定它是否会加快速度。绝大多数情况下,检查都会失败,这意味着它正在做额外的工作而没有任何好处。这可能会被分支预测所抵消,但在那种的情况下,检查通过的几次,它将使该分支预测所做的猜测无效,这实际上可能会减慢速度。换句话说,如果 JVM/CPU 尝试针对常见情况进行优化,您通常会一无所获,并且在极少数情况下您实际上会伤害自己(这就是您要优化的情况)。如果它不尝试优化该常见情况,那么您在大多数比较中都会为了一组相当罕见的比较而伤害自己。 </speculative>

【讨论】:

  • count 已经被检查为围绕我的代码的 if() 块的条件。如果某些字符串是从同一字符串的子字符串构建的,它们将具有相同的 char[] 并且可能具有相同的计数和偏移量。
  • 在计数问题上是对的,我的眼睛只是浏览了代码。我提到可能有相同的char[]countoffset——这不是常见的情况。发布此答案后,我阅读了@LouisWasserman 的链接,但这是关于这种“优化”问题的好帖子。
  • 我想 anObject 与被比较的 String 相同,或者被比较的 Object 不是 String 也不是常见的情况——但它们都在 equals( ) 方法。这是在开始在线性 O(n) 时间内发生的一系列比较之前,在常数 O(1) 中再进行一次(有时是两次以上)比较之间的权衡;并且所有 4 个变量都已加载。
  • @UFL1138:instanceof 检查不是优化,它是正确性所必需的。我想,快速的== 支票比你的机会更大,它节省了所有的工作......
【解决方案2】:

在 Java 7 中 (see this article),substring() 不再对返回的 String 使用相同的后备数组。您仍然需要检查每个字符。基本上,String 支持 char[] 永远不会共享,所以你不能

this.value == other.value

【讨论】:

  • 那么,您的意思是,在 Java 7 中,它会创建背景数组的副本,而不是使用相同的后备数组?
  • 不过,您可以将该代码替换为 java.util.Arrays.equals(value, other.value)
  • @gparyani 是的,被调用的构造函数使用Arrays.copyOfRange
  • @gparyani equals 方法几乎是等价的。
  • 我有点怀疑;您链接到的那篇文章没有引用任何来源并且查看OpenJDK's implementation,它肯定会继续像往常一样表现(共享字符[])。
【解决方案3】:

我不明白这个问题。
char[]String 的内部成员。如果 2 个字符串引用相同(应该是因为您应该使用实习字符串),则 char[] 将是相同的。
但是对于不同的情况,为什么您希望 char[] 是相同的参考?字符串是不可变的,2 个不同的 String 对象不可能共享对同一个支持数组的引用。
此外,即使对子字符串使用这种条件检查也没有任何意义。
我不知道其中一个答案中提到的 Java 7 的变化,但在这种情况下检查支持数组的相等性是错误的。
String 对象不仅是支持数组,而且是它的当前偏移量、长度等。
因此,作为子字符串的 2 个字符串对象可能由相同的字符数组支持可以很好地包含 不同(子)字符串作为内容 - 不同相同字符数组中的偏移量

【讨论】:

  • 你应该看看the source code for String。考虑以下内容: String x = "I am a String, guy!";字符串 y = x.substring(5,7);字符串 z = x.substring(5,7); y 和 x 将具有相同的偏移量和计数,并且与 x 共享相同的 char[]。
  • @UFL1138:如果你这样做x.substring(5,7)y.substring(0,3)你仍然有相同的缓冲区但不同的字符串
【解决方案4】:

对后备字符数组进行这样的检查很可能是多余的并且不是必需的。

在两种情况下,支持字符数组对象可能是相同的对象(因为其他指向的子字符串方法总是创建一个新的支持字符数组)。

定义字符串字面量

String a = "Hello";
a.equals("Hello"); // Backing array of "Hello" string literal 
                   // will be same as that of variable a

在这种情况下,equals 方法将在检查后备字符数组之前确定字符串在下一行是否相等。

if (this == anObject) { // From String.equals method
    return true;
}

使用String拷贝构造函数创建另一个String对象

请注意,以下代码块没有实用价值,永远无法在真实代码中完成。

String a = "Hello;
String b = new String(a);
a.equals(b);

因此,与其做额外的检查来确定字符数组是否相同,不如假设如果 String 对象不同,它们总是不同的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-27
    • 2023-04-05
    • 1970-01-01
    • 2016-03-22
    • 2014-05-25
    • 2020-02-18
    相关资源
    最近更新 更多