在 Java 中,String
类的 equals
方法被设计用来比较两个字符串对象的内容是否相等。在 Java 8 及以后的版本中,String
类的 equals
方法的实现采用了优化,这种优化主要体现在对字符串内部编码的处理上。
首先,equals
方法会检查传入的对象是否与当前对象是同一个对象(即它们的引用是否相同),如果是,那么直接返回 true
。
if (anObject == this) {
return true;
}
接下来,equals
方法会检查传入的对象是否是 String
类型:
if (!(anObject instanceof String)) {
return false;
}
如果传入的对象是 String
类型,那么 equals
方法会进一步检查当前字符串和传入字符串的编码是否相同。这里涉及到 Java 字符串的内部表示。Java 字符串内部可能使用两种编码方式:UTF-16 或 Latin1。这个检查是为了确保在比较字符串内容时,使用的是相同的编码方式。
final String anotherString = (String)anObject;
int len1 = value.length;
int len2 = anotherString.value.length;
if (len1 != len2) {
return false;
}
上面的代码段中,value
和 anotherString.value
是字符串的内部字符数组。如果两个字符串的长度不同,那么它们的内容肯定不同,因此直接返回 false
。
接下来,equals
方法会根据字符串的编码方式(UTF-16 或 Latin1)来比较字符数组的内容。如果编码方式相同,并且字符数组的内容也相同,那么返回 true
,否则返回 false
。
if (COMPACT_STRINGS) {
if (coder != anotherString.coder) {
return false;
}
return StringUTF16.equals(value, anotherString.value);
} else {
return StringLatin1.equals(value, anotherString.value);
}
这里需要注意的是,COMPACT_STRINGS
是一个布尔值,用于指示是否使用紧凑字符串表示。紧凑字符串表示是 Java 9 中引入的一种优化,它减少了字符串对象的内存占用。
总的来说,Java String
类的 equals
方法的设计是为了高效地比较两个字符串对象的内容是否相等。它通过一系列的检查和优化,确保在比较字符串内容时能够尽可能地减少不必要的计算。
在这里有完整的 String 源码(好像是 JDK13 的)。
很容易搜索到 COMPACT_STRINGS 的定义和说明:
这一段说明大致上可以看明白,如果
COMPACT_STRINGS
是false
,那value
固定是按 UTF16 进行编码的。而且,大致可以猜到跟coder
相关。然后关于
coder
,可以找到对应的源码其实就两个值,分别表示
LATIN1
和UTF16
。Java 的字段和函数(方法)是可以同名的,所以除了字段 coder 外,也可以找到函数 coder()。这个就很好理解了:那么这句话就好理解了:
如果
COMPACT_STRINGS == false
,那就是按 UTF16,继续看下一个条件。如果这个条件不成立,就要看coder
是否相等,如果不等,那直接判“否”。这里如果不好理解,可以自己手写代码,把这个逻辑判断拆开来理解。然后下一个条件,
StringLatin1.equals(value, aString.value)
,直接使用 Latin1 编码规则来对字符串的内部数据value
来进行比较。至于 value 是什么,代码里也很清楚的写了,就是用来存储字符的。所以整个比较的逻辑就出来了
UTF16 如何进行比较我,说实话我也没明白。不过只要确定了编码规则一样,而且 StringLatin1 是按字节进行对比的话,那其实并不需要关注它本身是什么编码规则。毕竟按字节对比是最底层的方法。当然前提是去看 StringLatin1 代码看看他的实现是不是跟假设的一样。
并不是循环,如果在
"a".equals("a")
的时候发现了需要比较的是"GBK"
,那说明在比较的过程中有编码的比较。这里产生的比较似乎只有在StringLatin1.equals
中才会发生。所以到底是怎么回事,可能还是要看看StringLatin1
的源代码。另外,既然 DEBUG 跟踪了,那可以看看调用栈,并且在调用栈中逐级去查找调用点的代码。