为什么 Java 的 String.equals() 方法使用两个计数变量?
Why does Java's String.equals() method use two counting variables?
我刚刚查看了 Java 的 String
class 的实现,以下内容让我感到奇怪:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { // Here n is being decremented...
if (v1[i] != v2[i])
return false;
i++; // while i is being incremented
}
return true;
}
}
return false;
}
这可以很容易地用一个计数变量来实现,而 n
将是有效的最终变量,如下所示:
while (i != n) {
if (v1[i] != v2[i])
return false;
i++;
}
还有这个,它完全摆脱了 i
:
while (n-- != 0) {
if (v1[n] != v2[n])
return false;
}
它与 0 相比是否比另一个变量便宜(微小的一点),或者是否有任何其他特殊原因来说明为什么以这种方式实现它?
我认为它必须与 JDK 7.
之前的 substring 实施有关
当时,底层字符数组大小不一定是字符串大小。有两个字段 offset
和 count
(分别为 i
和 j
)显示基础数组中 this
字符串的位置,因此该方法是:
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;
}
在上面提到的改变之后,这个方法也必须改变,所以他们只是将 n 固定为现在的数组长度:
int n = value.length;
并删除了 j
(因为不再有偏移):
int i = 0;
现在因为 i
必须在使用 2 次后递增,所以它在单独的语句中递增:
if (v1[i] != v2[i])
return false;
i++;
我想就是这样,有一个更简洁的实现,如果它是从头开始编写它是显而易见的,但考虑到它是由另一个变化推动的变化......Oracle 人和我们一样是普通人:)
基准测试
至于基准测试 String#equals
与 Arrays#equals(char[], char[]
) 我认为我们必须比较苹果与苹果,所以我将比较 2 个字符数组的两种方法放在一起:
public static void main(String[] args) {
final Random random = new Random();
final int arrays = 10000;
final int chars = 1000;
// generate the arrays
char[][] array = new char[arrays][chars];
for (int i = 0; i < arrays; i++) {
for (int j = 0; j < chars; j++) {
array[i][j] = (char)(random.nextInt(94) + 33);
}
}
// compare using Arrays equals
long before = System.nanoTime();
for (int i = 0; i < arrays; i++) {
for (int j = 0; j < chars; j++) {
equals_Arrays(array[i], array[j]);
}
}
System.out.println(System.nanoTime() - before);
// compare using String equals
before = System.nanoTime();
for (int i = 0; i < arrays; i++) {
for (int j = 0; j < chars; j++) {
equals_String(array[i], array[j]);
}
}
System.out.println(System.nanoTime() - before);
}
private static boolean equals_Arrays(char[] a, char[] a2) {
if (a == a2)
return true;
if (a == null || a2 == null)
return false;
int length = a.length;
if (a2.length != length)
return false;
for (int i = 0; i < length; i++)
if (a[i] != a2[i])
return false;
return true;
}
private static boolean equals_String(char[] v1, char[] v2) {
if (v1 == v2)
return true;
if (v1 == null || v2 == null)
return false;
int length = v1.length;
if (length == v2.length) {
int i = 0;
while (length-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
return false;
}
在我的盒子上,我没有发现明显的差异。
这不是答案,只是一个伪标记:
public class CompString {
public static void main(String[] args) {
DecimalFormat df = new DecimalFormat("0.#################");
int count = 1000000;
int length = 20;
int runs = 10;
String[] strings = new String[count];
String[] copiedStrings = new String[count];
char[][] chars = new char[count][];
char[][] copiedChars = new char[count][];
for (int i = 0; i < count; i++) {
String str = RandomStringUtils.random(length);
strings[i] = new String(str);
copiedStrings[i] = new String(str);
chars[i] = Arrays.copyOf(str.toCharArray(), str.length());
copiedChars[i] = Arrays.copyOf(chars[i], chars[i].length);
}
System.out.println("Lets banchmark that !!");
int loop = 0;
while (loop++ < runs) {
System.out.println("Run #" + loop);
long t = 0;
long t0 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
strings[i].equals(copiedStrings[i]);
}
long t1 = System.currentTimeMillis();
t = t1 - t0;
System.out.println("Avg String.equals() duration: " + df.format(t / (double) count));
t0 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
Arrays.equals(chars[i], copiedChars[i]);
}
t1 = System.currentTimeMillis();
t = t1 - t0;
System.out.println("Avg Arrays.equals(char[] char[]) duration: " + df.format(t / (double) count));
System.out.println();
}
}
}
结果如下:
Run #1
Avg String.equals() duration: 0,000017
Avg Arrays.equals(char[] char[]) duration: 0,000013
Run #2
Avg String.equals() duration: 0,000037
Avg Arrays.equals(char[] char[]) duration: 0
Run #3
Avg String.equals() duration: 0,000002
Avg Arrays.equals(char[] char[]) duration: 0
Run #4
Avg String.equals() duration: 0,000003
Avg Arrays.equals(char[] char[]) duration: 0
Run #5
Avg String.equals() duration: 0,000002
Avg Arrays.equals(char[] char[]) duration: 0
Run #6
Avg String.equals() duration: 0,000003
Avg Arrays.equals(char[] char[]) duration: 0
Run #7
Avg String.equals() duration: 0,000003
Avg Arrays.equals(char[] char[]) duration: 0
Run #8
Avg String.equals() duration: 0,000003
Avg Arrays.equals(char[] char[]) duration: 0
Run #9
Avg String.equals() duration: 0,000002
Avg Arrays.equals(char[] char[]) duration: 0
Run #10
Avg String.equals() duration: 0,000002
Avg Arrays.equals(char[] char[]) duration: 0
所以因为 String.equals
比比较底层数组(使用单个迭代器变量)需要更多的时间,我们可以在这里排除一些神奇的 JVM 优化问题(但它可能是过去吗?)。
它不是大多数带有 HotSpot 的真实 Java 虚拟机的真实代码。 String.equals是非常重要的方法,它是通过intrinsic实现的。它具有特定于平台的本机实现。您可以在此处找到完整列表 src/share/vm/classfile/vmSymbols.hpp(参见 do_intrinsic)
我刚刚查看了 Java 的 String
class 的实现,以下内容让我感到奇怪:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { // Here n is being decremented...
if (v1[i] != v2[i])
return false;
i++; // while i is being incremented
}
return true;
}
}
return false;
}
这可以很容易地用一个计数变量来实现,而 n
将是有效的最终变量,如下所示:
while (i != n) {
if (v1[i] != v2[i])
return false;
i++;
}
还有这个,它完全摆脱了 i
:
while (n-- != 0) {
if (v1[n] != v2[n])
return false;
}
它与 0 相比是否比另一个变量便宜(微小的一点),或者是否有任何其他特殊原因来说明为什么以这种方式实现它?
我认为它必须与 JDK 7.
之前的 substring 实施有关当时,底层字符数组大小不一定是字符串大小。有两个字段 offset
和 count
(分别为 i
和 j
)显示基础数组中 this
字符串的位置,因此该方法是:
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;
}
在上面提到的改变之后,这个方法也必须改变,所以他们只是将 n 固定为现在的数组长度:
int n = value.length;
并删除了 j
(因为不再有偏移):
int i = 0;
现在因为 i
必须在使用 2 次后递增,所以它在单独的语句中递增:
if (v1[i] != v2[i])
return false;
i++;
我想就是这样,有一个更简洁的实现,如果它是从头开始编写它是显而易见的,但考虑到它是由另一个变化推动的变化......Oracle 人和我们一样是普通人:)
基准测试
至于基准测试 String#equals
与 Arrays#equals(char[], char[]
) 我认为我们必须比较苹果与苹果,所以我将比较 2 个字符数组的两种方法放在一起:
public static void main(String[] args) {
final Random random = new Random();
final int arrays = 10000;
final int chars = 1000;
// generate the arrays
char[][] array = new char[arrays][chars];
for (int i = 0; i < arrays; i++) {
for (int j = 0; j < chars; j++) {
array[i][j] = (char)(random.nextInt(94) + 33);
}
}
// compare using Arrays equals
long before = System.nanoTime();
for (int i = 0; i < arrays; i++) {
for (int j = 0; j < chars; j++) {
equals_Arrays(array[i], array[j]);
}
}
System.out.println(System.nanoTime() - before);
// compare using String equals
before = System.nanoTime();
for (int i = 0; i < arrays; i++) {
for (int j = 0; j < chars; j++) {
equals_String(array[i], array[j]);
}
}
System.out.println(System.nanoTime() - before);
}
private static boolean equals_Arrays(char[] a, char[] a2) {
if (a == a2)
return true;
if (a == null || a2 == null)
return false;
int length = a.length;
if (a2.length != length)
return false;
for (int i = 0; i < length; i++)
if (a[i] != a2[i])
return false;
return true;
}
private static boolean equals_String(char[] v1, char[] v2) {
if (v1 == v2)
return true;
if (v1 == null || v2 == null)
return false;
int length = v1.length;
if (length == v2.length) {
int i = 0;
while (length-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
return false;
}
在我的盒子上,我没有发现明显的差异。
这不是答案,只是一个伪标记:
public class CompString {
public static void main(String[] args) {
DecimalFormat df = new DecimalFormat("0.#################");
int count = 1000000;
int length = 20;
int runs = 10;
String[] strings = new String[count];
String[] copiedStrings = new String[count];
char[][] chars = new char[count][];
char[][] copiedChars = new char[count][];
for (int i = 0; i < count; i++) {
String str = RandomStringUtils.random(length);
strings[i] = new String(str);
copiedStrings[i] = new String(str);
chars[i] = Arrays.copyOf(str.toCharArray(), str.length());
copiedChars[i] = Arrays.copyOf(chars[i], chars[i].length);
}
System.out.println("Lets banchmark that !!");
int loop = 0;
while (loop++ < runs) {
System.out.println("Run #" + loop);
long t = 0;
long t0 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
strings[i].equals(copiedStrings[i]);
}
long t1 = System.currentTimeMillis();
t = t1 - t0;
System.out.println("Avg String.equals() duration: " + df.format(t / (double) count));
t0 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
Arrays.equals(chars[i], copiedChars[i]);
}
t1 = System.currentTimeMillis();
t = t1 - t0;
System.out.println("Avg Arrays.equals(char[] char[]) duration: " + df.format(t / (double) count));
System.out.println();
}
}
}
结果如下:
Run #1
Avg String.equals() duration: 0,000017
Avg Arrays.equals(char[] char[]) duration: 0,000013
Run #2
Avg String.equals() duration: 0,000037
Avg Arrays.equals(char[] char[]) duration: 0
Run #3
Avg String.equals() duration: 0,000002
Avg Arrays.equals(char[] char[]) duration: 0
Run #4
Avg String.equals() duration: 0,000003
Avg Arrays.equals(char[] char[]) duration: 0
Run #5
Avg String.equals() duration: 0,000002
Avg Arrays.equals(char[] char[]) duration: 0
Run #6
Avg String.equals() duration: 0,000003
Avg Arrays.equals(char[] char[]) duration: 0
Run #7
Avg String.equals() duration: 0,000003
Avg Arrays.equals(char[] char[]) duration: 0
Run #8
Avg String.equals() duration: 0,000003
Avg Arrays.equals(char[] char[]) duration: 0
Run #9
Avg String.equals() duration: 0,000002
Avg Arrays.equals(char[] char[]) duration: 0
Run #10
Avg String.equals() duration: 0,000002
Avg Arrays.equals(char[] char[]) duration: 0
所以因为 String.equals
比比较底层数组(使用单个迭代器变量)需要更多的时间,我们可以在这里排除一些神奇的 JVM 优化问题(但它可能是过去吗?)。
它不是大多数带有 HotSpot 的真实 Java 虚拟机的真实代码。 String.equals是非常重要的方法,它是通过intrinsic实现的。它具有特定于平台的本机实现。您可以在此处找到完整列表 src/share/vm/classfile/vmSymbols.hpp(参见 do_intrinsic)