indexOf 大小写敏感吗?

93

indexOf(String)方法是否区分大小写?如果是,是否有不区分大小写的版本?


3
虽然我并不是一个非常重视性能的人(事实上,我认为性能调优有点邪恶),但每次调用 .toUpperCase() 方法都会复制一遍字符串,因此如果你在循环中使用它,请尽可能将其放在循环外面。请注意不要改变原来的意思。 - Bill K
19个回答

80

indexOf()方法都是区分大小写的。你可以通过在使用该方法之前将字符串转换为大写/小写来使它们(粗略地、以一种有缺陷但对许多情况有效的方式)不区分大小写:

s1 = s1.toLowerCase(Locale.US);
s2 = s2.toLowerCase(Locale.US);
s1.indexOf(s2);

6
在使用 toUpperCase 时要注意国际化问题(例如土耳其的 İ)。更合适的解决方案是使用 str.toUpperCase(Locale.US).indexOf(...)。 - James Van Huis
2
我非常确定,根据Unicode比较规则,进行大小写转换然后比较并不完全正确。这对于某些情况是有效的(即大小写折叠,通常仅在语法解析上下文中使用),但对于自然语言而言,可能存在特殊情况,其中两个应该相等的字符串,在大写或小写的情况下都不相等。然而,我无法立即想出任何例子。 - nielsm
8
不起作用。当将一些奇怪的国际字符转换为小写或大写时,它们会被转换成多个字符。例如:"ß".toUpperCase().equals("SS") - Simon
ß 并不是一个奇怪的字符,也不是国际通用字符,只在德国和奥地利使用。但是,正如 nielsm 三年前指出的那样,这是最好的方法之一,但并不是真正的大小写不敏感比较。 - Joey
不支持土耳其Unicode,这是从某人的电子邮件中直接获取的。 - Alexander Pogrebnyak
显示剩余2条评论

44

indexOf(String)方法区分大小写。

是的,它区分大小写:

@Test
public void indexOfIsCaseSensitive() {
    assertTrue("Hello World!".indexOf("Hello") != -1);
    assertTrue("Hello World!".indexOf("hello") == -1);
}

如果需要,是否有一个不区分大小写的版本呢?

没有。但是您可以在调用indexOf之前将两个字符串转换为小写字母:

@Test
public void caseInsensitiveIndexOf() {
    assertTrue("Hello World!".toLowerCase().indexOf("Hello".toLowerCase()) != -1);
    assertTrue("Hello World!".toLowerCase().indexOf("hello".toLowerCase()) != -1);
}

9
请务必使用Locale.US进行文化不变转换,我们在土耳其语环境下运行Java应用程序时遇到了足够的问题,请不要忘记。 - idursun
@idursun - 强制使用美国语言环境并不能解决问题,因为它仍然不能处理实际包含起始问题字符的字符串(例如"ı".toLowerCase(Locale.US).indexOf("I".toLowerCase(Locale.US))应该返回0,因为第一个字符串是土耳其小写字母"I",所以应当与第二个字符串中的大写字母"I"比较相等,但实际上返回-1,因为后者被转换成了 "i")。 - Jules

23

Apache Commons Lang库中的StringUtils类有一个忽略大小写的方法

indexOfIgnoreCase(CharSequence str, CharSequence searchStr)


这应该是一个被接受的答案,因为当前的答案对于某些包含Unicode控制字符的非ASCII字符串不起作用。例如,这适用于土耳其文本。在幕后,Apache使用regionMatches,它确实有效。 - Alexander Pogrebnyak

17

这是我的解决方案,它不会分配任何堆内存,因此应该比这里提到的大多数其他实现方法都快得多。

public static int indexOfIgnoreCase(final String haystack,
                                    final String needle) {
    if (needle.isEmpty() || haystack.isEmpty()) {
        // Fallback to legacy behavior.
        return haystack.indexOf(needle);
    }

    for (int i = 0; i < haystack.length(); ++i) {
        // Early out, if possible.
        if (i + needle.length() > haystack.length()) {
            return -1;
        }

        // Attempt to match substring starting at position i of haystack.
        int j = 0;
        int ii = i;
        while (ii < haystack.length() && j < needle.length()) {
            char c = Character.toLowerCase(haystack.charAt(ii));
            char c2 = Character.toLowerCase(needle.charAt(j));
            if (c != c2) {
                break;
            }
            j++;
            ii++;
        }
        // Walked all the way to the end of the needle, return the start
        // position that this was found.
        if (j == needle.length()) {
            return i;
        }
    }

    return -1;
}

这里是验证正确行为的单元测试。

@Test
public void testIndexOfIgnoreCase() {
    assertThat(StringUtils.indexOfIgnoreCase("A", "A"), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("a", "A"), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("A", "a"), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("a", "a"), is(0));

    assertThat(StringUtils.indexOfIgnoreCase("a", "ba"), is(-1));
    assertThat(StringUtils.indexOfIgnoreCase("ba", "a"), is(1));

    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", " Royal Blue"), is(-1));
    assertThat(StringUtils.indexOfIgnoreCase(" Royal Blue", "Royal Blue"), is(1));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "royal"), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "oyal"), is(1));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "al"), is(3));
    assertThat(StringUtils.indexOfIgnoreCase("", "royal"), is(-1));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", ""), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "BLUE"), is(6));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "BIGLONGSTRING"), is(-1));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "Royal Blue LONGSTRING"), is(-1));  
}

这个回答如何回答问题? - Quality Catalyst
8
答案是“不,indexOf没有大小写不敏感的版本”。然而,我在这里添加了解决方案,因为人们将会寻找解决方案来到这个页面。我提供了带有测试用例的解决方案,让下一个遇到同样问题的人可以使用我的代码解决。这就是为什么Stack Overflow很有用,对吧?我有十年的编写高性能代码经验,其中一半在Google工作。我刚刚免费向社区提供了一个经过充分测试的解决方案来帮助大家。 - Zach Vorhies
3
这正是我感兴趣的内容。我发现这比Apache Commons版本快了大约10-15%。如果我可以多次点赞,我会这样做的。谢谢! - Jeff Williams
谢谢Jeff,我很高兴它给了你很多价值。还有其他人推荐这篇提供解决方案的帖子应该排在前面。如果有人喜欢我的代码,我谦虚地请求您为此解决方案投票支持。 - Zach Vorhies
2
这里缺少一个测试用例:assertThat(StringUtils.indexOfIgnoreCase("ı" /* 土耳其小写字母 I,U+0131 */, "I"), is(0)); - Jules

17

是的,indexOf 是区分大小写的。

我发现实现大小写不敏感的最好方法是:

String original;
int idx = original.toLowerCase().indexOf(someStr.toLowerCase());

这将执行不区分大小写的 indexOf() 操作。


5
不要这样做。原因是original.toLowerCase().length()并不总是等于original.length()。结果idx不能正确地映射回original - Cheok Yan Cheng

11

是的,它区分大小写。您可以通过在搜索之前将您的字符串和字符串参数都转换为大写来执行不区分大小写的indexOf

String str = "Hello world";
String search = "hello";
str.toUpperCase().indexOf(search.toUpperCase());

请注意,toUpperCase在某些情况下可能无法正常工作。例如:

String str = "Feldbergstraße 23, Mainz";
String find = "mainz";
int idxU = str.toUpperCase().indexOf (find.toUpperCase ());
int idxL = str.toLowerCase().indexOf (find.toLowerCase ());

idxU将会是20,这是错误的。idxL将会是19,这是正确的。问题的原因在于 toUpperCase() 会把 "ß" 字符转换为两个字符 "SS",导致索引失误。

因此,建议始终使用 toLowerCase()。


1
坚持使用小写并没有帮助:如果你将 find 改为 "STRASSE",它在小写变体中根本找不到,但在大写版本中可以正确地找到。 - Jules

4

一旦返回索引值,您将如何使用它?

如果您要使用它来操作字符串,那么是否可以使用正则表达式代替?

import static org.junit.Assert.assertEquals;    
import org.junit.Test;

public class StringIndexOfRegexpTest {

    @Test
    public void testNastyIndexOfBasedReplace() {
        final String source = "Hello World";
        final int index = source.toLowerCase().indexOf("hello".toLowerCase());
        final String target = "Hi".concat(source.substring(index
                + "hello".length(), source.length()));
        assertEquals("Hi World", target);
    }

    @Test
    public void testSimpleRegexpBasedReplace() {
        final String source = "Hello World";
        final String target = source.replaceFirst("(?i)hello", "Hi");
        assertEquals("Hi World", target);
    }
}

在这里缺乏点赞的情况让我感到惊讶。在一个充斥着错误答案的页面中,这是仅有的三个能够正确运行的答案之一。 - Jules

2
@Test
public void testIndexofCaseSensitive() {
    TestCase.assertEquals(-1, "abcDef".indexOf("d") );
}

这甚至没有回答完整的问题... 它甚至没有说测试是否通过.... - jjnguy
2
你说的对,我没有这么做。我有点希望这会促使最初的提问者自己运行测试,并养成这个习惯。 - Paul McKenzie
2
好吧,这样也行……但我认为投票给一个真正给出答案的问题比投票给一个测试更好。StackOverflow试图成为一个代码问答库。因此,完整的答案会是最好的选择。 - jjnguy
1
@jjnguy:我一直以为发布测试的人都是发布通过测试的测试。@dfa 做了类似的事情。(但 @dfa 的答案更完整)。 - Tom
但他也发布了一些文字(描述)...那些通常是有帮助的。 - jjnguy

2

我曾遇到相同的问题。我尝试了正则表达式和 Apache 的 StringUtils.indexOfIgnoreCase 方法,但两者速度都比较慢... 因此我自己编写了一个简短的方法:

public static int indexOfIgnoreCase(final String chkstr, final String searchStr, int i) {
    if (chkstr != null && searchStr != null && i > -1) {
          int serchStrLength = searchStr.length();
          char[] searchCharLc = new char[serchStrLength];
          char[] searchCharUc = new char[serchStrLength];
          searchStr.toUpperCase().getChars(0, serchStrLength, searchCharUc, 0);
          searchStr.toLowerCase().getChars(0, serchStrLength, searchCharLc, 0);
          int j = 0;
          for (int checkStrLength = chkstr.length(); i < checkStrLength; i++) {
                char charAt = chkstr.charAt(i);
                if (charAt == searchCharLc[j] || charAt == searchCharUc[j]) {
                     if (++j == serchStrLength) {
                           return i - j + 1;
                     }
                } else { // faster than: else if (j != 0) {
                         i = i - j;
                         j = 0;
                    }
              }
        }
        return -1;
  }

根据我的测试,如果你的搜索字符串比较短,它会快得多...如果您有任何改进或错误建议,请告诉我...(因为我在应用程序中使用此代码;-)

这实际上非常聪明,因为搜索字符串将比要搜索的文本短得多,而且它只创建搜索字符串的大写和小写版本。谢谢! - fiffy
在我的测试中,这比StringUtils版本慢得多。然而,Zach的答案要快10-15%左右。 - Jeff Williams
这个解决方案比Zach Vorhies提供的那个快大约10%。感谢您提供的这个解决方案。 - gogognome
该解决方案在存在转换为大写后长度发生变化的字符串时无法产生正确答案(例如,如果您搜索“ß”,它将在任何包含单个大写“S”的字符串中找到它),或者对于使用替代大小写的文本(例如,indexOfIgnoreCase(“İ”,“i”)应返回0,因为İ是土耳其文本中i的正确大写形式,但实际上返回-1,因为i被大写为更常见的I)。 - Jules

2

是的,我相当确定。使用标准库解决这个问题的一种方法是:

int index = str.toUpperCase().indexOf("FOO"); 

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