了解CaseInsensitiveComparator中的逻辑
任何人都可以解释String.java
的以下代码,特别是为什么有三个if
语句(我标记为//1
, //2
和//3
)?
private static class CaseInsensitiveComparator implements Comparator, java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1=s1.length(), n2=s2.length(); for (int i1=0, i2=0; i1<n1 && i2<n2; i1++, i2++) { char c1 = s1.charAt(i1); char c2 = s2.charAt(i2); if (c1 != c2) {/////////////////////////1 c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) {/////////////////////////2 c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) {/////////////////////////3 return c1 - c2; } } } } return n1 - n2; } }
通常情况下,我们希望将案例转换一次并进行比较并完成。 但是,代码将case转换两次,原因在注释中声明了不同的方法public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
:
不幸的是,转换为大写不适用于格鲁吉亚字母表,后者对案例转换有奇怪的规则。 所以我们需要在退出前进行最后一次检查。
附录
regionMatches
的代码与CaseInsenstiveComparator
的代码有一些区别,但基本上做同样的事情。 为了交叉检查,下面引用了该方法的完整代码:
public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) { char ta[] = value; int to = offset + toffset; char pa[] = other.value; int po = other.offset + ooffset; // Note: toffset, ooffset, or len might be near -1>>>1. if ((ooffset < 0) || (toffset < 0) || (toffset > (long)count - len) || (ooffset > (long)other.count - len)) { return false; } while (len-- > 0) { char c1 = ta[to++]; char c2 = pa[po++]; if (c1 == c2) { continue; } if (ignoreCase) { // If characters don't match but case may be ignored, // try converting both characters to uppercase. // If the results match, then the comparison scan should // continue. char u1 = Character.toUpperCase(c1); char u2 = Character.toUpperCase(c2); if (u1 == u2) { continue; } // Unfortunately, conversion to uppercase does not work properly // for the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before // exiting. if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) { continue; } } return false; } return true; }
从Unicode技术标准 :
此外,由于自然语言的变幻莫测,有两种不同的Unicode字符具有相同的大写或小写的情况
因此,仅仅比较两个字符的大写是不够的,因为它们可能具有不同的大写和相同的小写
简单的powershell检查可以得到一些结果。 检查示例代码点73和304:
char ch1 = (char) 73; //LATIN CAPITAL LETTER I char ch2 = (char) 304; //LATIN CAPITAL LETTER I WITH DOT ABOVE System.out.println(ch1==ch2); System.out.println(Character.toUpperCase(ch1)==Character.toUpperCase(ch2)); System.out.println(Character.toLowerCase(ch1)==Character.toLowerCase(ch2));
输出:
false false true
所以“İ”和“我”并不相同。 两个字符都是大写的。 但是它们共享相同的小写字母:“i”,这给出了在不区分大小写的比较中将它们视为相同值的理由。
在另一个答案中,默认语言环境已经举例说明了为什么只比较大写是不够的,即ASCII字母“I”和带有点“İ”的大写字母I。
现在您可能想知道, 为什么它们不仅仅比较小写而不是大写和小写,如果它捕获的情况多于大写? 答案是,它没有捕获更多的情况,它只是找到不同的情况。
取字母“ı”( (char)305
,小无点i)和ascii“i”。 它们是不同的,它们的小写字母不同,但它们共享相同的大写字母“I”。
最后,比较资本I和点“İ”与小无点我“ı”。 它们的大写字母(“İ”与“I”)和它们的小写字母(“i”与“ı”)都不匹配,但它们的大写字母的小写字母是相同的(“I”)。 我发现了另一种情况,如希腊字母“Θ”和“θ”(字符1012和977)中的这种现象。
因此,真正不区分大小写的比较甚至无法检查原始字符的大写字母和小写字母,但必须检查大写字母的小写字母。
请考虑以下字符: f
和F
初始if
语句将返回false
因为它们不匹配。 但是,如果您将两个字符都大写,则会获得F
和F
然后他们会匹配。 例如, c
和G
。
代码很有效。 如果两个字符已匹配(因此第一个if
语句),则无需将两个字符大写。 但是,如果它们不匹配,我们需要检查它们是否仅在大小写中是不同的(因此第二个if
语句)。
最终的if
语句用于某些字母表(例如格鲁吉亚语),其中大写是一个复杂的事情。 说实话,我不太了解它是如何工作的(只要相信Oracle的确如此!)。
在上述不区分大小写比较的情况下假设s1 =“Apple”和s2 =“apple”在这种情况下’A’!=’a’,因为两个字符的ascii值不同,那么它将两个字符都改为大写并再次比较然后循环继续获得n1-n2 = 0的最终值,因此字符串变得相同。如果字符在第二次检查中不相等则假设
if (c1 != c2) { return c1 - c2; }
返回两个字符的ascii值的差异。