为什么String.equals不检查char[]值的相等性?

6

我正在查看java.lang.String的源代码,并注意到equals方法没有检查每个字符串后面的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和可能的国际化字符串的情况下。

有人知道他们为什么选择不用这种方式实现吗?

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

考虑以下代码:

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

你最终会面临这样的情况: Debugger Expressions 此外,显然为了满足一些基准测试,Java 7u6已经取消了字符串的值共享功能。因此,如果你花时间使用String.substring()而不是字符串连接使代码运行在合理的时间内(或根本无法运行),你就完了。

做到这一点的方法是比较每个字符。 - Sotirios Delimanolis
也许这句话没有意义,因为我们总是使用clone()数组。 - nachokk
4
在Java 7之前,它可能改善了极少数情况的性能,例如foo.substring(i, j).equals(foo.substring(i, j)),但这也会增加一个额外的检查,用于处理更常见的情况,即字符串数组不相等的情况,这很可能比节省的时间花费更多平均时间。请参见例如这篇博客文章 - Louis Wasserman
@SotiriosDelimanolis 我不是在问它们是否相等(); 我是在问它们是否是同一个对象。 - UFL1138
@LouisWasserman 我认为这个优化和他的例子之间最重要的区别在于,这个优化通过运行一行更多的 O(1) 代码(1 或 2 次比较,取决于我们的幸运程度)来防止一个 O(n) 的操作;而他的例子只能防止一个 O(n) 的操作,其中 n=0 或 n=1(本质上)。 - UFL1138
即便如此,“n”通常不会很大,而且这种情况比通常情况要少得多。 - Louis Wasserman
4个回答

1

好的,你需要检查char[]offsetcount(字符串长度)。由于char[]只能在String类内部创建,因此所有这三个值都相等的唯一方法是String自己从自己创建一个替身。你可以让它这样做(例如:new String("why?")),但这不是常见用例。

我甚至不确定这样做是否能加速任何东西。大部分时间,检查都会失败,这意味着它正在做无用功。这可能会被分支预测所抵消,但在那种情况下,检查通过的几次将使该分支预测的猜测失效,这实际上会拖慢速度。换句话说,如果JVM / CPU尝试针对常见情况进行优化,通常你将得不到任何收益,在罕见情况下反而会伤及自己(这正是你要优化的地方)。如果它不尝试优化这种常见情况,为了一个相当罕见的比较集,你会在大多数比较中受到伤害。

计数已经作为if()块的条件来检查了,该块围绕着我的代码。如果一些字符串是由相同字符串的子字符串构建的,它们将具有相同的char[],并且可以具有相同的计数和偏移量。 - UFL1138
啊,关于计数问题是真的,我的眼睛只是匆匆看了一下代码。我提到过可能会有相同的 char[]countoffset,但这并不是常见情况。在我发布这个答案之后,我阅读了 @LouisWasserman 的链接,但这是一个关于这种“优化”问题的好帖子。 - yshavit
我想,对于一个Object与被比较的String相同的情况并不常见,或者被比较的对象不是一个String -- 但在equals()方法中它们都会被检查。这是在常数O(1)中进行一次或两次比较的权衡,在启动线性O(n)时间内发生的一系列比较之前;而且这4个变量已经加载完毕。 - UFL1138
1
@UFL1138:instanceof检查不是一种优化,它是必需的以保证正确性。我猜测,快速的 == 检查比你的更有可能,并且可以节省所有的工作... - maaartinus

0
在Java 7中(请参阅本文),substring()不再为返回的String使用相同的后备数组。您仍然需要检查每个字符。基本上,String后备char[]永远不会共享,因此您无法
this.value == other.value

那么,您的意思是,在Java 7中,它会创建背景数组的副本,而不是使用相同的背景数组? - gparyani
然而,您可以使用 java.util.Arrays.equals(value, other.value) 替换该代码。 - gparyani
@gparyani equals 方法基本上是等同的。 - Sotirios Delimanolis
@UFL1138 Oracle JDK不再共享支持数组。 - Sotirios Delimanolis
@UFL1138 请看一下我在这个错误报告和我在这个问题中找到的链接。 - Sotirios Delimanolis
显示剩余3条评论

0

我不理解这个问题。
char[]String的内部成员。如果两个字符串引用相同(应该是这样,因为你应该使用intern strings),那么char[]将是相同的。
但对于不同的实例,为什么您希望char[]是相同的引用?字符串是不可变的,因此两个不同的字符串对象不能共享对同一支持数组的引用。
此外,即使对于子字符串,使用此条件检查也没有意义。
我不知道答案中提到的Java 7中的更改,但在这种情况下检查支持数组的相等性是错误的。
一个字符串对象不仅是支持数组,还包括其当前偏移量、长度等。
因此,由于子字符串而产生的两个字符串对象可能由相同的字符数组支持,但可以包含作为内容的不同(子)字符串 - 在相同的字符数组中具有不同的偏移量


你应该查看字符串的源代码。考虑以下内容:String x =“我是一个字符串,伙计!”; String y = x.substring(5,7); String z = x.substring(5,7); y和x将具有相同的偏移量和计数,并共享与x相同的char[]。 - UFL1138
@UFL1138: 如果您使用x.substring(5,7)y.substring(0,3),则仍然有相同的缓冲区但不同的字符串。 - Cratylus

0

在后备字符数组上进行这样的检查很可能是多余的,也不是必需的。

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

定义字符串字面量

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 a = "Hello;
String b = new String(a);
a.equals(b);

因此,与其进行额外的检查以确定字符数组是否相同,如果字符串对象不同,安全地假定它们始终不同。

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