indexOf с учетом регистра?


81

Учитывает ли регистр в методе indexOf (String)? Если да, то есть ли его версия без учета регистра?


3
Не то чтобы я большой парень по производительности или что-то в этом роде (я на самом деле считаю настройку производительности своего рода злом), но .toUpperCase копирует вашу строку каждый раз, когда вы ее вызываете, поэтому, если вы делаете это в цикле, попробуйте переместить .toUpperCase петли, если возможно.
Bill K

Ответы:


75

Все indexOf()методы чувствительны к регистру. Вы можете сделать их (грубо говоря, сломанным способом, но работающими во многих случаях) нечувствительными к регистру, предварительно преобразовав ваши строки в верхний / нижний регистр:

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

4
Остерегайтесь проблем интернационализации (например, турецкого İ) при использовании toUpperCase. Более правильное решение - использовать str.toUpperCase (Locale.US) .indexOf (...);
Джеймс Ван Хьюис,

2
Я совершенно уверен, что преобразование регистра и последующее сравнение не совсем корректно в соответствии с правилами сравнения Unicode. Он работает для некоторых вещей (а именно для сворачивания регистра, который обычно используется только в контекстах синтаксического анализа), но для естественного языка могут быть особые случаи, когда две строки, которые должны сравниваться, не совпадают, как в верхнем, так и в нижнем регистре. Однако я не могу придумать никаких примеров с места в карьер.
nielsm

7
Не получится. Некоторые странные международные символы преобразуются в несколько символов при преобразовании в нижний / верхний регистр. Например:"ß".toUpperCase().equals("SS")
Саймон

ß - вряд ли странный символ, и он вряд ли интернациональный, поскольку используется только в Германии и Австрии. Но да, это так же хорошо, как и получается, но на самом деле сравнение без учета регистра, как уже указывал Нильсм три года назад.
Joey

Не работает с турецким юникодом, который приходит прямо из чьей-то электронной почты.
Александр Погребняк

43

Учитывает ли регистр в методе 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);
}

8
о, пожалуйста, пожалуйста, не забудьте использовать инвариантное преобразование языка и региональных параметров с помощью Locale.US, у нас было достаточно проблем с java-приложениями, работающими под турецким языком.
idursun

@idursun - принуждение к региональному стандарту США не решает проблему, потому что он по-прежнему не работает для строк, которые действительно содержат символы, с которых проблематично начинать (например, "ı".toLowerCase(Locale.US).indexOf("I".toLowerCase(Locale.US))должен возвращать 0, потому что первая строка является турецкой строчной буквой "I", и поэтому должен сравниваться как верхний регистр "I"во втором, но возвращает -1, потому что последний "i"вместо этого преобразуется в ).
Жюль

20

В классе StringUtils библиотеки Apache Commons Lang есть метод игнорирования регистра.

indexOfIgnoreCase (строка CharSequence, строка поиска CharSequence)


Это должен быть принятый ответ, поскольку текущий ответ не работает для некоторых строк, отличных от ascii, которые содержат управляющие символы Unicode. Например, это работает для текста, написанного на турецком языке. За кадром Apache использует regionMatches, и это действительно работает.
Александр Погребняк

17

Да, с indexOfучетом регистра.

Я нашел лучший способ сделать регистр нечувствительным:

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

Это будет нечувствительно к регистру indexOf().


2
Нет, никогда не делай этого. Причина в том, что original.toLowerCase().length()не всегда равно original.length(). Результат idxне может быть корректно сопоставлен с original.
Cheok Yan Cheng

14

Вот мое решение, которое не выделяет память кучи, поэтому оно должно быть значительно быстрее, чем большинство других реализаций, упомянутых здесь.

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

7
Ответ - «нет, не существует версий indexOf без учета регистра». Однако я добавил решение здесь, потому что люди будут искать эту страницу в поисках решений. Я сделал свое решение доступным с тестовыми примерами, чтобы следующий человек мог использовать мой код для решения той же проблемы. Вот почему переполнение стека полезно? У меня есть десятилетний опыт написания высокопроизводительного кода, половина из которых - в Google. Я просто бесплатно дал хорошо проверенное решение, чтобы помочь сообществу.
Зак Ворхис

3
Это именно то, что меня интересовало. Я обнаружил, что это примерно на 10-15% быстрее, чем версия Apache Commons. Если бы я мог проголосовать за него еще много раз, я бы это сделал. Благодаря!
Джефф Уильямс,

Спасибо, Джефф, я рад, что это принесло тебе большую пользу. Есть и другие, которые рекомендуют, чтобы этот пост, предлагающий решение, был наверху. Если кому-то еще понравится мой код, я смиренно прошу вас проголосовать за это решение.
Зак Ворхис

2
Вот недостающий тестовый пример:assertThat(StringUtils.indexOfIgnoreCase("ı" /* Turkish lower-case I, U+0131 */, "I"), is(0));
Жюль

10

Да, это с учетом регистра. Вы можете сделать регистр нечувствительным indexOf, преобразовав строку и параметр String в верхний регистр перед поиском.

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", он вообще не найдет его в нижнем регистре, но правильно найдет его в верхнем регистре.
Жюль

3

Что вы делаете с возвращенным значением индекса?

Если вы используете его для управления своей строкой, не могли бы вы вместо этого использовать регулярное выражение?

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);
    }
}

Удивлен отсутствием здесь положительных голосов. На странице, где преобладают неправильные ответы, это один из трех, который действительно работает правильно.
Жюль

2

Я только что посмотрел на источник. Он сравнивает символы, поэтому он чувствителен к регистру.


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

Это даже не дает полного ответа на вопрос ... это даже не говорит о том, пройдёт ли тест ...
jjnguy

2
Вы правы, я этого не сделал, я как бы надеялся, что это побудит автора, задавшего первоначальный вопрос, провести тест сам и, возможно, войдет в привычку
Пол Маккензи

2
Что ж, это нормально ... но я бы сказал, что было бы лучше проголосовать за вопрос, который действительно дает ответ, чем за тест. StackOverflow пытается быть репозиторием кода Q и A. Поэтому полные ответы были бы лучше.
jjnguy

1
@jjnguy: Мне всегда казалось, что люди, которые публикуют тесты, публикуют тесты, которые проходят. @dfa вроде как сделал то же самое. (Но ответ @dfa более полный).
Tom

Но он также опубликовал несколько слов (описание) ... Обычно они полезны.
jjnguy

2

Да, я почти уверен, что это так. Один из способов решения этой проблемы с использованием стандартной библиотеки:

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

2

Была такая же проблема. Я пробовал регулярное выражение и apache StringUtils.indexOfIgnoreCase-Method, но оба были довольно медленными ... Поэтому я сам написал короткий метод ...:

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;
  }

Согласно моим тестам, он намного быстрее ... (по крайней мере, если ваш searchString довольно короткий). если у вас есть предложения по улучшению или ошибки, было бы неплохо сообщить мне ... (поскольку я использую этот код в приложении ;-)


На самом деле это очень умно, поскольку строка поиска будет значительно короче, чем текст для поиска, и она создает только версию строки поиска в верхнем и нижнем регистре. Спасибо тебе за это!
fiffy

В моем тестировании это значительно медленнее, чем версия StringUtils. Однако ответ Зака ​​на 10-15% быстрее.
Джефф Уильямс,

Это решение примерно на 10% быстрее, чем решение Зака ​​Ворхиса. Спасибо за это решение.
gogognome

Это решение не дает правильного ответа при наличии строк, длина которых изменяется при преобразовании в верхний регистр (например, если вы ищете «ß», он найдет его в любой строке, содержащей одну заглавную «S») или для текста, который использует альтернативные заглавные буквы (например, indexOfIgnoreCase("İ","i")должен возвращать 0, потому что İэто правильное использование заглавных букв iдля турецкого текста, но вместо этого возвращает -1, потому что iиспользуется заглавная буква для более распространенного I).
Жюль

1

На первый вопрос уже много раз ответили. Да, все String.indexOf()методы чувствительны к регистру.

Если вам нужен чувствительный к языку, indexOf()вы можете использовать Collator . В зависимости от установленного вами значения силы вы можете получить сравнение без учета регистра, а также рассматривать буквы с диакритическими знаками так же, как и буквы без диакритических знаков и т. Д. Вот пример того, как это сделать:

private int indexOf(String original, String search) {
    Collator collator = Collator.getInstance();
    collator.setStrength(Collator.PRIMARY);
    for (int i = 0; i <= original.length() - search.length(); i++) {
        if (collator.equals(search, original.substring(i, i + search.length()))) {
            return i;
        }
    }
    return -1;
}

Удивлен отсутствием здесь положительных голосов. На странице, где преобладают неправильные ответы, это один из трех, который действительно работает правильно.
Жюль

1

Подводя итог, 3 решения:

  • используя toLowerCase () или toUpperCase
  • используя StringUtils из apache
  • с использованием регулярного выражения

Теперь, что мне было интересно, какой из них самый быстрый? Я предполагаю в среднем первый.


0

Но написать одно несложно:

public class CaseInsensitiveIndexOfTest extends TestCase {
    public void testOne() throws Exception {
        assertEquals(2, caseInsensitiveIndexOf("ABC", "xxabcdef"));
    }

    public static int caseInsensitiveIndexOf(String substring, String string) {
        return string.toLowerCase().indexOf(substring.toLowerCase());
    }
}

Как отмечалось выше, это не позволяет правильно определить, что "ı"это вариант в нижнем регистре (только не по умолчанию для большинства языков) "I". Или же, если работать на множество машин на местность , где "ı" есть по умолчанию, он будет не в состоянии заметить , что "i"также является строчным вариантом "I".
Жюль

0

Преобразование обеих строк в нижний регистр обычно не представляет большого труда, но это будет медленным, если некоторые из строк будут длинными. И если вы сделаете это в цикле, это будет очень плохо. По этой причине я бы рекомендовал indexOfIgnoreCase.


0
 static string Search(string factMessage, string b)
        {

            int index = factMessage.IndexOf(b, StringComparison.CurrentCultureIgnoreCase);
            string line = null;
            int i = index;
            if (i == -1)
            { return "not matched"; }
            else
            {
                while (factMessage[i] != ' ')
                {
                    line = line + factMessage[i];
                    i++;
                }

                return line;
            }

        }

1
Это похоже , что это может быть C #
Уэстон

0

Вот версия, очень похожая на версию Apache StringUtils:

public int indexOfIgnoreCase(String str, String searchStr) {
    return indexOfIgnoreCase(str, searchStr, 0);
}

public int indexOfIgnoreCase(String str, String searchStr, int fromIndex) {
    // /programming/14018478/string-contains-ignore-case/14018511
    if(str == null || searchStr == null) return -1;
    if (searchStr.length() == 0) return fromIndex;  // empty string found; use same behavior as Apache StringUtils
    final int endLimit = str.length() - searchStr.length() + 1;
    for (int i = fromIndex; i < endLimit; i++) {
        if (str.regionMatches(true, i, searchStr, 0, searchStr.length())) return i;
    }
    return -1;
}

0

Я хотел бы заявить права на ЕДИНОЕ и единственное решение, опубликованное на данный момент, которое действительно работает. :-)

Три класса проблем, которые нужно решить.

  1. Нетранзитивные правила сопоставления для нижнего и верхнего регистра. Турецкая проблема I часто упоминалась в других ответах. Согласно комментариям в источнике Android для String.regionMatches, для грузинских правил сравнения требуется дополнительное преобразование в нижний регистр при сравнении на равенство без учета регистра.

  2. Случаи, когда в формах верхнего и нижнего регистра разное количество букв. Практически все решения, опубликованные до сих пор, терпят неудачу в этих случаях. Пример: немецкие STRASSE и Straße имеют равенство без учета регистра, но имеют разную длину.

  3. Сила связывания акцентированных персонажей. Локаль И контекст влияет независимо от того, совпадают ли акценты. Во французском языке заглавная форма «é» - это «E», хотя наблюдается тенденция к использованию заглавных акцентов. В канадском французском языке заглавная форма «é» - это «É» без исключения. Пользователи в обеих странах ожидают, что "e" будет соответствовать "é" при поиске. Совместимость символов с диакритическими знаками и без диакритических знаков зависит от региона. Теперь подумайте: равно ли "E" "É"? Да. Оно делает. Во всяком случае, во французских странах.

В настоящее время я использую android.icu.text.StringSearchдля правильной реализации предыдущих реализаций операций indexOf без учета регистра.

Пользователи, не использующие Android, могут получить доступ к тем же функциям через пакет ICU4J, используя com.ibm.icu.text.StringSearchкласс.

Будьте осторожны, чтобы ссылаться на классы в правильном пакете icu ( android.icu.textили com.ibm.icu.text), поскольку Android и JRE имеют классы с одинаковыми именами в других пространствах имен (например, Collator).

    this.collator = (RuleBasedCollator)Collator.getInstance(locale);
    this.collator.setStrength(Collator.PRIMARY);

    ....

    StringSearch search = new StringSearch(
         pattern,
         new StringCharacterIterator(targetText),
         collator);
    int index = search.first();
    if (index != SearchString.DONE)
    {
        // remember that the match length may NOT equal the pattern length.
        length = search.getMatchLength();
        .... 
    }

Тестовые случаи (языковой стандарт, шаблон, целевой текст, ожидаемый результат):

    testMatch(Locale.US,"AbCde","aBcDe",true);
    testMatch(Locale.US,"éèê","EEE",true);

    testMatch(Locale.GERMAN,"STRASSE","Straße",true);
    testMatch(Locale.FRENCH,"éèê","EEE",true);
    testMatch(Locale.FRENCH,"EEE","éèê",true);
    testMatch(Locale.FRENCH,"éèê","ÉÈÊ",true);

    testMatch(new Locale("tr-TR"),"TITLE","tıtle",true);  // Turkish dotless I/i
    testMatch(new Locale("tr-TR"),"TİTLE","title",true);  // Turkish dotted I/i
    testMatch(new Locale("tr-TR"),"TITLE","title",false);  // Dotless-I != dotted i.

PS: Насколько я могу судить, сила привязки PRIMARY должна действовать правильно, когда правила, специфичные для локали, различают символы с диакритическими знаками и без них в соответствии с правилами словаря; но я не знаю, какой язык использовать для проверки этой предпосылки. Мы будем благодарны за предоставленные тестовые примеры.


1
Если вы хотите использовать двойную лицензию для своего кода, сделайте это через другую платформу и укажите там ссылку. Огромный кусок юридического языка, добавленный в конце каждого ответа, добавляет невероятное количество беспорядка в Stack Overflow.
meagar

Тогда, возможно, вам следует найти более эффективный способ решения проблемы CC-BY-SA, применяемой к фрагментам кода,
Робин Дэвис,

Вам также кажется неуместным удалять предоставленные мной лицензии на фрагменты кода, авторские права на которые принадлежат мне.
Робин Дэвис,

-2

indexOf чувствителен к регистру. Это потому, что он использует метод equals для сравнения элементов в списке. То же самое касается "содержать" и "удалить".


Исходный вопрос касается метода indexOf String.
Джон Топли,

Я не знал, о чем он говорил. Я не осознавал этого, пока другие люди что-то не сказали. Однако принцип все тот же.
Робби

2
Нет, это не так. Внутренняя часть метода indexOf String сравнивает символы, а не объекты, поэтому он не использует метод equals.
John Topley
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.