Как проверить, содержит ли строка другую строку без учета регистра в Java?

387

Скажем, у меня есть две строки,

String s1 = "AbBaCca";
String s2 = "bac";

Я хочу выполнить возврат чека, который s2содержится внутри s1. Я могу сделать это с:

return s1.contains(s2);

Я уверен, что contains()это чувствительно к регистру, но я не могу определить это наверняка, прочитав документацию. Если это так, то я полагаю, что мой лучший метод будет что-то вроде:

return s1.toLowerCase().contains(s2.toLowerCase());

Помимо всего этого, есть ли другой (возможно, лучший) способ сделать это, не заботясь о чувствительности к регистру?

Аарон
источник
DrJava был бы чрезвычайно простым способом проверить это, когда документация вас не устраивает . Просто введите пару тестов в окно «Взаимодействия», и вы должны это выяснить.
EfForEffort
17
Я думаю, что вы ответили на свой вопрос. Я не думаю, что какое-либо из решений ниже лучше, чем это. Но они определенно медленнее.
Николай Димитров
7
Ваше решение проще, чем любое из ответов
LobsterMan
2
Ответ, который я и многие здесь ищем, находится в вашем вопросе.
Лалит Фауздар
1
Ваш пример - самый простой, самый читаемый и, вероятно, лучший способ сделать это - лучше, чем любой из ответов, которые я вижу.
user1258361

Ответы:

320

Да, содержит с учетом регистра. Вы можете использовать java.util.regex.Pattern с флагом CASE_INSENSITIVE для сопоставления без учета регистра:

Pattern.compile(Pattern.quote(wantedStr), Pattern.CASE_INSENSITIVE).matcher(source).find();

РЕДАКТИРОВАТЬ: Если s2 содержит специальные символы регулярных выражений (из которых их много), важно сначала процитировать его. Я исправил свой ответ, так как это первый, который увидят люди, но проголосуйте за Мэтта Квила, так как он указал на это.

Дэйв Л.
источник
23
Как указано в документации для Pattern.CASE_INSENSITIVE, это работает только для символов ASCII (т. Е. «Ä» не будет соответствовать «ä»). UNICODE_CASEДля достижения этого необходимо дополнительно указать флаг.
Филипп Вендлер
72
этот подход использует Patternболее производительный, чем s1.toLowerCase().contains(s2.toLowerCase())?
Раджат Гупта
6
@ user01 Я выполнил анализ скорости. Смотрите мой ответ для результатов (я также показал более быстрое решение): stackoverflow.com/a/25379180/1705598
icza
10
Мне было бы более понятно, что происходило бы, если бы у нас были лучшие имена переменных:Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find()
Джон Бауэрс
5
@ user01 корректность важнее производительности, а использование toLowerCase даст потенциально неверные результаты (например, при сравнении определенного греческого текста, содержащего букву Sigma, которая имеет две строчные формы для одной и той же прописной формы).
Klitos Kyriacou
267

Одна проблема с ответом Дейва Л. - это когда s2 содержит разметку регулярных выражений, например \d, и т. Д.

Вы хотите вызвать Pattern.quote () на s2:

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
Мэтт перепел
источник
1
Приятно поймать Мэтта. Мне любопытно узнать, какой метод более эффективен: строчные буквы или ваше шаблонное решение. Разве использование шаблона менее эффективно для одного сравнения, но более эффективно для нескольких сравнений?
Аарон
41
Метод .toLowerCase (). Contains (), вероятно, будет быстрее в большинстве случаев. Вероятно, я бы предпочел этот стиль для более низкой сложности.
Matt Quail
3
@AaronFerguson Да, действительно, toLowerCase().contains()быстрее. Я выполнил некоторый анализ скорости, см. Мой ответ для результатов: stackoverflow.com/a/25379180/1705598
icza
2
@MattQuail нет никакого смысла в том, чтобы быть быстрее, если он может быть неправильным. Например, греческая заглавная сигма имеет две строчные формы (в зависимости от того, идет ли она в конце слова или нет), и при попытке выполнить сопоставление подстроки без учета регистра, где подстрока заканчивается сигмой, вы можете легко получить неверный Результаты.
Klitos Kyriacou
Я думаю, что мы должны добавить Pattern.UNICODE_CASEфлаг тоже. Не могли бы вы подтвердить это?
Тарик Нугрохотомо
160

Ты можешь использовать

org.apache.commons.lang3.StringUtils.containsIgnoreCase("AbBaCca", "bac");

Библиотека Apache Commons очень полезна для такого рода вещей. И этот конкретный может быть лучше, чем регулярные выражения, так как регулярное выражение всегда дорого с точки зрения производительности.

muhamadto
источник
1
Кто-нибудь знает, уважает ли это язык?
Чарльз Вуд
12
@CharlesWood Он делегирует String.regionMatches, который использует посимвольные преобразования, так что нет. Более того, containsIgnoreCase("ß", "ss")возвращает -1, что неправильно во всех локалях (немецкий язык «sharp s»
пишется
Какой будет правильный способ сравнения немецких слов тогда? Похоже, что это один язык, который усложняет любые способы сравнения строк: P
chomp
1
Кстати, немецкий язык был официально расширен с большой буквы в 2017 году: de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F . На немецких клавиатурах введите Shift + Alt Gr + ß -> test: ẞ 😁
Kawu
119

Быстрая реализация: использование String.regionMatches()

Использование регулярных выражений может быть относительно медленным. Это (медленно) не имеет значения, если вы просто хотите проверить в одном случае. Но если у вас есть массив или набор из тысяч или сотен тысяч строк, все может стать довольно медленным.

Представленное ниже решение не использует ни регулярные выражения, ни toLowerCase() (что также является медленным, потому что оно создает другие строки и просто выбрасывает их после проверки).

Решение основано на методе String.regionMatches (), который кажется неизвестным. Он проверяет, Stringсовпадают ли 2 области, но важно то, что он также имеет перегрузку с удобным ignoreCaseпараметром.

public static boolean containsIgnoreCase(String src, String what) {
    final int length = what.length();
    if (length == 0)
        return true; // Empty string is contained

    final char firstLo = Character.toLowerCase(what.charAt(0));
    final char firstUp = Character.toUpperCase(what.charAt(0));

    for (int i = src.length() - length; i >= 0; i--) {
        // Quick check before calling the more expensive regionMatches() method:
        final char ch = src.charAt(i);
        if (ch != firstLo && ch != firstUp)
            continue;

        if (src.regionMatches(true, i, what, 0, length))
            return true;
    }

    return false;
}

Анализ скорости

Этот анализ скорости не означает ракетостроение, это лишь приблизительная картина того, насколько быстры разные методы.

Я сравниваю 5 методов.

  1. Наш метод содержитIgnoreCase () .
  2. Путем преобразования обеих строк в нижний регистр и вызов String.contains().
  3. Путем преобразования исходной строки в нижний регистр и вызова String.contains() с предварительно кэшированной подстрокой . Это решение уже не так гибко, потому что оно тестирует предстроку подстроки.
  4. Используя регулярное выражение (принятый ответ Pattern.compile().matcher().find()...)
  5. Использование регулярного выражения, но с предварительно созданным и кэшированным Pattern. Это решение уже не так гибко, потому что оно тестирует предопределенную подстроку.

Результаты (вызвав метод 10 миллионов раз):

  1. Наш метод: 670 мс
  2. 2x toLowerCase () и содержит (): 2829 мс
  3. 1x toLowerCase () и содержит () с кэшированной подстрокой: 2446 мс
  4. Регулярное выражение: 7180 мс
  5. Регулярное выражение с кэшированием Pattern: 1845 мс

Результаты в таблице:

                                            RELATIVE SPEED   1/RELATIVE SPEED
 METHOD                          EXEC TIME    TO SLOWEST      TO FASTEST (#1)
------------------------------------------------------------------------------
 1. Using regionMatches()          670 ms       10.7x            1.0x
 2. 2x lowercase+contains         2829 ms        2.5x            4.2x
 3. 1x lowercase+contains cache   2446 ms        2.9x            3.7x
 4. Regexp                        7180 ms        1.0x           10.7x
 5. Regexp+cached pattern         1845 ms        3.9x            2.8x

Наш метод в 4 раза быстрее по сравнению со строчными буквами и использованием contains(), в 10 раз быстрее по сравнению с использованием регулярных выражений, а также в 3 раза быстрее, даже еслиPattern он предварительно кэширован (и теряет гибкость проверки произвольной подстроки).


Анализ тестового кода

Если вам интересно, как был проведен анализ, вот вам готовое приложение:

import java.util.regex.Pattern;

public class ContainsAnalysis {

    // Case 1 utilizing String.regionMatches()
    public static boolean containsIgnoreCase(String src, String what) {
        final int length = what.length();
        if (length == 0)
            return true; // Empty string is contained

        final char firstLo = Character.toLowerCase(what.charAt(0));
        final char firstUp = Character.toUpperCase(what.charAt(0));

        for (int i = src.length() - length; i >= 0; i--) {
            // Quick check before calling the more expensive regionMatches()
            // method:
            final char ch = src.charAt(i);
            if (ch != firstLo && ch != firstUp)
                continue;

            if (src.regionMatches(true, i, what, 0, length))
                return true;
        }

        return false;
    }

    // Case 2 with 2x toLowerCase() and contains()
    public static boolean containsConverting(String src, String what) {
        return src.toLowerCase().contains(what.toLowerCase());
    }

    // The cached substring for case 3
    private static final String S = "i am".toLowerCase();

    // Case 3 with pre-cached substring and 1x toLowerCase() and contains()
    public static boolean containsConverting(String src) {
        return src.toLowerCase().contains(S);
    }

    // Case 4 with regexp
    public static boolean containsIgnoreCaseRegexp(String src, String what) {
        return Pattern.compile(Pattern.quote(what), Pattern.CASE_INSENSITIVE)
                    .matcher(src).find();
    }

    // The cached pattern for case 5
    private static final Pattern P = Pattern.compile(
            Pattern.quote("i am"), Pattern.CASE_INSENSITIVE);

    // Case 5 with pre-cached Pattern
    public static boolean containsIgnoreCaseRegexp(String src) {
        return P.matcher(src).find();
    }

    // Main method: perfroms speed analysis on different contains methods
    // (case ignored)
    public static void main(String[] args) throws Exception {
        final String src = "Hi, I am Adam";
        final String what = "i am";

        long start, end;
        final int N = 10_000_000;

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCase(src, what);
        end = System.nanoTime();
        System.out.println("Case 1 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsConverting(src, what);
        end = System.nanoTime();
        System.out.println("Case 2 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsConverting(src);
        end = System.nanoTime();
        System.out.println("Case 3 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCaseRegexp(src, what);
        end = System.nanoTime();
        System.out.println("Case 4 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCaseRegexp(src);
        end = System.nanoTime();
        System.out.println("Case 5 took " + ((end - start) / 1000000) + "ms");
    }

}
icza
источник
6
+1, но имейте в виду, что он не подходит для ß(немецкая резкая S; с заглавной буквы SS), а также для некоторых других символов (см. Источник String.regionMatches, который пытается оба преобразования).
Maaartinus
2
Ваш всегда тестирует одни и те же строки, что не совсем честное сравнение. «Я есть» всегда в середине, что может иметь или не иметь значение для различных методов поиска. Лучше было бы генерировать случайные строки, а также сообщать о скорости, когда подстрока отсутствует.
2
Это похоже на метод Apache StringUtils: grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/…
alain.janinm
1
@ alain.janinm Я не вижу сходства. Единственное, что кажется «близким» StringUtils.containsIgnoreCase()- это то, что и мое решение, и Apache используют regionMatches()метод (в цикле), но даже это не то же самое, что я вызываю String.regionMatches()и Apache CharSequenceUtils.regionMatches().
2013 г.
2
@icza CharSequenceUtils.regionMatchesпросто звонит на String.regionMatchesсамом деле. В любом случае, я хотел дать информацию, что если кто-то уже использует библиотеку StringUtils, он может просто вызвать ее, потому что это эффективный способ, как вы докажете это с помощью своего теста. Если бы я не использовал Apache lib, я бы окончательно использовал ваш метод;)
alain.janinm
22

Более простым способом сделать это (не беспокоясь о сопоставлении с образцом) было бы преобразование обоих Strings в нижний регистр:

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
    System.out.println("It's a match!");
}
Фил
источник
4
Символьный регистр зависит от языка, что означает, что он будет работать на вашем компьютере, но потерпит неудачу для клиента :). см. комментарий @Adriaan Koster.
Кроиз
1
@kroiz, это зависит от того, откуда взялась строка. Сравнение «foobar» и «FOO» всегда будет совпадать, однако, если вы сравниваете информацию, вводимую пользователем, или контент для конкретного языка, то вы правы - разработчик должен быть осторожен.
Фил
16

Да, это достижимо

String s1 = "abBaCca";
String s2 = "bac";

String s1Lower = s1;

//s1Lower is exact same string, now convert it to lowercase, I left the s1 intact for print purposes if needed

s1Lower = s1Lower.toLowerCase();

String trueStatement = "FALSE!";
if (s1Lower.contains(s2)) {

    //THIS statement will be TRUE
    trueStatement = "TRUE!"
}

return trueStatement;

Этот код вернет строку "ИСТИНА!" как выяснилось, что ваши персонажи содержались.

Бильбо Бэггинс
источник
12
Большим недостатком использования toLowerCase () является то, что результат зависит от текущей локали. См: javapapers.com/core-java/...
Adriaan Костер
4
Вопрос на самом деле содержит лучшее решение, так как этот вопрос не для строчных s2. Не говоря о таких деталях, как эта, эта не компилируется, и если она это делает, она возвращает строку.
Maaartinus
3

Вот некоторые Unicode-дружественные, которые вы можете сделать, если вы используете ICU4j. Я предполагаю, что «игнорировать регистр» сомнителен для имен методов, потому что, хотя первичные сравнения силы действительно игнорируют регистр, он описывается как специфика, зависящая от локали. Но мы надеемся, что это зависит от локали, как и ожидал пользователь.

public static boolean containsIgnoreCase(String haystack, String needle) {
    return indexOfIgnoreCase(haystack, needle) >= 0;
}

public static int indexOfIgnoreCase(String haystack, String needle) {
    StringSearch stringSearch = new StringSearch(needle, haystack);
    stringSearch.getCollator().setStrength(Collator.PRIMARY);
    return stringSearch.first();
}
Trejkaz
источник
3

Я сделал тест, чтобы найти регистр без учета совпадения строки. У меня есть Вектор из 150000 объектов со Строкой в ​​одном поле, и я хотел найти подмножество, соответствующее строке. Я попробовал три метода:

  1. Конвертировать все в нижний регистр

    for (SongInformation song: songs) {
        if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) {
                ...
        }
    }
  2. Используйте метод String match ()

    for (SongInformation song: songs) {
        if (song.artist.matches("(?i).*" + pattern + ".*")) {
        ...
        }
    }
  3. Используйте регулярные выражения

    Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
    Matcher m = p.matcher("");
    for (SongInformation song: songs) {
        m.reset(song.artist);
        if (m.find()) {
        ...
        }
    }

Сроки результаты:

  • Нет попыток совпадения: 20 мсек

  • Чтобы понизить соответствие: 182 мсек

  • Струнные совпадения: 278 мсек

  • Регулярное выражение: 65 мсек

Регулярное выражение выглядит самым быстрым для этого варианта использования.

Ян Ньюмарх
источник
Хорошо, что вы поставили сроки результатов. Все говорят, насколько медленным является регулярное выражение, но на самом деле это очень быстро, если вам нужно скомпилировать регулярное выражение только один раз.
Woot
1

Существует простой и краткий способ использования флага регулярных выражений (без учета регистра {i}):

 String s1 = "hello abc efg";
 String s2 = "ABC";
 s1.matches(".*(?i)"+s2+".*");

/*
 * .*  denotes every character except line break
 * (?i) denotes case insensitivity flag enabled for s2 (String)
 * */
Mr.Q
источник
0

Я не уверен, что ваш главный вопрос здесь, но да, .contains учитывает регистр.

SCdF
источник
0
String container = " Case SeNsitive ";
String sub = "sen";
if (rcontains(container, sub)) {
    System.out.println("no case");
}

public static Boolean rcontains(String container, String sub) {

    Boolean b = false;
    for (int a = 0; a < container.length() - sub.length() + 1; a++) {
        //System.out.println(sub + " to " + container.substring(a, a+sub.length()));
        if (sub.equalsIgnoreCase(container.substring(a, a + sub.length()))) {
            b = true;
        }
    }
    return b;
}

По сути, это метод, который принимает две строки. Предполагается, что это версия с учетом регистра (не чувствительная к регистру). При использовании метода contains вы хотите увидеть, содержится ли одна строка в другой.

Этот метод берет строку, которая является «sub», и проверяет, равна ли она подстрокам строки контейнера, длина которых равна «sub». Если вы посмотрите на forцикл, вы увидите, что он перебирает подстроки (длина «sub») над строкой контейнера.

Каждая итерация проверяет, находится ли подстрока строки контейнера equalsIgnoreCaseв подпрограмме.

Seth
источник
в основном это метод, который принимает две строки. Предполагается, что это не чувствительная к регистру версия содержит (). при использовании метода contains вы хотите увидеть, содержится ли одна строка в другой. этот метод берет строку «sub» и проверяет, равна ли она подстрокам строки контейнера, длина которых равна «sub». если вы посмотрите на цикл for, то увидите, что он перебирает подстроки (длина «sub») над строкой контейнера. каждая итерация проверяет, является ли подстрока строки контейнера равнозначной для подстроки.
Seth
@ Наверное, тебе стоит добавить это в свой ответ.
Парень в шляпе
2
Это самый медленный метод за всю историю ... и для немецкого не подходит.
Маартин
0

Если вам придется искать строку ASCII в другой строке ASCII, например в URL , вы найдете мое решение лучше. Я проверил метод icza и мой на скорость, и вот результаты:

  • Случай 1 занял 2788 мс - regionMatches
  • Случай 2 занял 1520 мс - мой

Код:

public static String lowerCaseAscii(String s) {
    if (s == null)
        return null;

    int len = s.length();
    char[] buf = new char[len];
    s.getChars(0, len, buf, 0);
    for (int i=0; i<len; i++) {
        if (buf[i] >= 'A' && buf[i] <= 'Z')
            buf[i] += 0x20;
    }

    return new String(buf);
}

public static boolean containsIgnoreCaseAscii(String str, String searchStr) {
    return StringUtils.contains(lowerCaseAscii(str), lowerCaseAscii(searchStr));
}
Revertron
источник
0
import java.text.Normalizer;

import org.apache.commons.lang3.StringUtils;

public class ContainsIgnoreCase {

    public static void main(String[] args) {

        String in = "   Annulée ";
        String key = "annulee";

        // 100% java
        if (Normalizer.normalize(in, Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").toLowerCase().contains(key)) {
            System.out.println("OK");
        } else {
            System.out.println("KO");
        }

        // use commons.lang lib
        if (StringUtils.containsIgnoreCase(Normalizer.normalize(in, Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", ""), key)) {
            System.out.println("OK");
        } else {
            System.out.println("KO");
        }

    }

}
sgrillon
источник
Спасибо за этот фрагмент кода, который может предоставить некоторую ограниченную краткосрочную помощь. Правильное объяснение значительно улучшило бы его долгосрочную ценность, показав, почему это хорошее решение проблемы, и сделало бы его более полезным для будущих читателей с другими, похожими вопросами. Пожалуйста, измените свой ответ, чтобы добавить некоторые объяснения, в том числе предположения, которые вы сделали.
Тоби Спайт
0
"AbCd".toLowerCase().contains("abcD".toLowerCase())
Тахир Атамуратов
источник
2
Можете ли вы улучшить свой ответ, объяснив, как ваш код решает проблему?
Isuka
1
Этот ответ уже предлагался во многих других, более подробных ответах на этот вопрос, предоставленных другими. Я не думаю, что этот ответ служит какой-либо цели здесь.
DaveyDaveDave
0

Мы можем использовать поток с anyMatch и содержит Java 8

public class Test2 {
    public static void main(String[] args) {

        String a = "Gina Gini Protijayi Soudipta";
        String b = "Gini";

        System.out.println(WordPresentOrNot(a, b));
    }// main

    private static boolean WordPresentOrNot(String a, String b) {
    //contains is case sensitive. That's why change it to upper or lower case. Then check
        // Here we are using stream with anyMatch
        boolean match = Arrays.stream(a.toLowerCase().split(" ")).anyMatch(b.toLowerCase()::contains);
        return match;
    }

}
Судипта Датта
источник
0

или вы можете использовать простой подход и просто преобразовать регистр строки в регистр подстроки, а затем использовать метод contains.

Сайед Салман Хасан
источник
-1
String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());
IVY
источник
-1

Вы можете просто сделать что-то вроде этого:

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
Эрик Кондела
источник