如何在基本类型中忽略大小写比较字符

39

我正在编写以下代码:

String name1 = fname.getText().toString();
String name2 = sname.getText().toString();
aru = 0;

count1 = name1.length();
count2 = name2.length();
for (i = 0; i < count1; i++)
{  
    for (j = 0; j < count2; j++)
    { 
        if (name1.charAt(i)==name2.charAt(j))
            aru++;
    }
    if(aru!=0)
        aru++;
}

我想比较两个字符串中的字符而忽略大小写。简单使用IgnoreCase不起作用。添加65的ASCII值也无法解决问题。我该如何做?


3
你可以使用Character.toLowerCaseCharacter.toUpperCase将所有字符转换为小写或大写并进行比较。 - idiottiger
1
@idiottiger 这在英语中有效,但并不适用于每一种语言。 - Peter Bruins
6个回答

68
Java API 的 Character 类拥有多种可用的函数。
你可以将字符转换为小写字母,无论是左侧还是右侧。
Character.toLowerCase(name1.charAt(i)) == Character.toLowerCase(name2.charAt(j))

还有一些方法可以用来验证字母是大写还是小写:

Character.isUpperCase('P')
Character.isLowerCase('P') 

4
这并不适用于每种语言,例如对于土耳其语来说会失败。 - Peter Bruins
1
@PeterBruins - 你能否分享一段会导致代码失败的土耳其文本样例? - MasterJoe
1
在土耳其语中,“İ”(带点的大写I)是“I”的大写版本。更多信息请参见:https://haacked.com/archive/2012/07/05/turkish-i-problem-and-why-you-should-care.aspx/ - Peter Bruins

16

使用toLowerCase,无论是在字符串还是在字符中,都不能完全正确地执行作业。问题在于,在任一大小写字母中都存在变体字形,并且根据您大写或小写字形的方式,可能会保留或不保留这些字形。甚至当您说忽略大小写比较两个小写字母的两个变体时,您所指的含义也不清楚:它们是否相同?(请注意,还有混合大小写的字形:\u01c5、\u01c8、\u01cb、\u01f2或Dž、Lj、Nj、Dz,但只要将它们视为与其完全大写或完全小写的变体相同,此处建议的任何方法都可以解决它们。)

使用Char还存在另一个问题:有大约80个代码点用单个Char无法表示,并且它们是大/小写变体(40个变体)。至少Java的代码点大小写检测到了这些。因此,您需要获取代码点并更改这些代码点的大小写。

但代码点对于变体字形没有帮助。

无论如何,这里是由于变体而出现问题的字形的完整列表,显示它们针对6种变体方法的表现:

  1. Character toLowerCase
  2. Character toUpperCase
  3. String toLowerCase
  4. String toUpperCase
  5. String equalsIgnoreCase
  6. Character toLowerCase(toUpperCase)(反之亦然)

对于这些方法,S表示将变体视为彼此相同,D表示将变体视为不同。

Behavior     Unicode                             Glyphs
===========  ==================================  =========
1 2 3 4 5 6  Upper  Lower  Var Up Var Lo Vr Lo2  U L u l l2
- - - - - -  ------ ------ ------ ------ ------  - - - - -
D D D D S S  \u0049 \u0069 \u0130 \u0131         I i İ ı   
S D S D S S  \u004b \u006b \u212a                K k K     
D S D S S S  \u0053 \u0073        \u017f         S s   ſ   
D S D S S S  \u039c \u03bc        \u00b5         Μ μ   µ   
S D S D S S  \u00c5 \u00e5 \u212b                Å å Å     
D S D S S S  \u0399 \u03b9        \u0345 \u1fbe  Ι ι   ͅ ι 
D S D S S S  \u0392 \u03b2        \u03d0         Β β   ϐ   
D S D S S S  \u0395 \u03b5        \u03f5         Ε ε   ϵ   
D D D D S S  \u0398 \u03b8 \u03f4 \u03d1         Θ θ ϴ ϑ   
D S D S S S  \u039a \u03ba        \u03f0         Κ κ   ϰ   
D S D S S S  \u03a0 \u03c0        \u03d6         Π π   ϖ   
D S D S S S  \u03a1 \u03c1        \u03f1         Ρ ρ   ϱ   
D S D S S S  \u03a3 \u03c3        \u03c2         Σ σ   ς   
D S D S S S  \u03a6 \u03c6        \u03d5         Φ φ   ϕ   
S D S D S S  \u03a9 \u03c9 \u2126                Ω ω Ω     
D S D S S S  \u1e60 \u1e61        \u1e9b         Ṡ ṡ   ẛ   
进一步复杂化问题的是,除非您知道自己在土耳其,否则没有办法正确获取土耳其字母I的大小写(即带点和不带点的版本不同)。这些方法都不能给出正确的行为,除非您知道所在地区(即非土耳其:i和I忽略大小写相同;土耳其不同)。
总体而言,使用 toUpperCase 给出了最接近的近似值,因为您只有五个大写变量(或者四个,不包括土耳其)。
您也可以尝试特别拦截这五个棘手的情况,并仅对它们调用 toUpperCase(toLowerCase(c))。如果您仔细选择保护程序(如果 c < 0x130 || c > 0x212B,则只需 toUpperCase,然后通过其他替代方案进行工作),则对于低范围内的字符,只会产生约20%的速度损失(与将单个字符转换为字符串并equalsIgnoreCase它们相比,损失4倍),如果危险区域有很多,只会产生约2倍的损失。除了带点的 I 之外,您的状态还不错。当然,如果您可以对更大的字符串使用 equalsIgnoreCase,那么最好这样做。
以下是执行此操作的 Scala 示例代码:
def elevateCase(c: Char): Char = {
  if (c < 0x130 || c > 0x212B) Character.toUpperCase(c)
  else if (c == 0x130 || c == 0x3F4 || c == 0x2126 || c >= 0x212A)
    Character.toUpperCase(Character.toLowerCase(c))
  else Character.toUpperCase(c)
}

9

在使用字符串之前,你可以改变它的大小写,像这样:

String name1 = fname.getText().toString().toLowerCase(); 
String name2 = sname.getText().toString().toLowerCase();

然后继续进行其余的操作。

6
注意Turkish locale问题 - axtavt
如果我们处理的是“巨大”的字符串序列,那么字符串替换不会很慢吗? - MasterJoe

3
这是JDK的做法(改编自OpenJDK 8,String.java/regionMatches):
static boolean charactersEqualIgnoringCase(char c1, char c2) {
  if (c1 == c2) return true;

  // If characters don't match but case may be ignored,
  // try converting both characters to uppercase.
  char u1 = Character.toUpperCase(c1);
  char u2 = Character.toUpperCase(c2);
  if (u1 == u2) return true;

  // 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.
  return Character.toLowerCase(u1) == Character.toLowerCase(u2);
}

我想对于土耳其语也适用。


“char” 类型已经过时,应该避免使用,因为它无法表示 Unicode 定义的一半字符。 - Basil Bourque
这真的是个问题吗?Java可以将代码点表示为2个字符的组合。反正还会使用什么呢? - Stefan Reich
请查看codePoint方法,例如String::codePointsStrimg::codePointAt。这些方法使用的是int而不是char - Basil Bourque
请阅读《绝对最少每个软件开发人员必须了解的Unicode和字符集(无任何借口!)》。链接:https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/。 - Basil Bourque

3
当比较字符、转换为小写或大写时,您需要考虑土耳其语问题:
建议先将其转换为字符串,然后使用不变区域性(至少在大多数情况下)进行小写转换。 public final static Locale InvariantLocale = new Locale(Empty, Empty, Empty); str.toLowerCase(InvariantLocale)
请参见类似的C#:string.ToLower()和string.ToLowerInvariant() 注意:不要使用String.equalsIgnoreCase。http://nikolajlindberg.blogspot.co.il/2008/03/beware-of-java-comparing-turkish.html

1

提供通用方法,比较两个字符串中某一位置上的字符(忽略大小写)。

public static boolean isEqualIngoreCase(char one, char two){
    return Character.toLowerCase(one)==Character .toLowerCase(two);
}

public static boolean isEqualStringCharIgnoreCase(String one, String two, int position){
    char oneChar = one.charAt(position);
    char twoChar = two.charAt(position);
    return isEqualIngoreCase(oneChar, twoChar);
}

函数调用

boolean isFirstCharEqual = isEqualStringCharIgnoreCase("abc", "ABC", 0)

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