Як перевірити, чи містить рядок інший рядок у випадку нечутливого до регістру Java?


386

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

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
Ваш приклад - найпростіший, найчитабельніший і, мабуть, найкращий спосіб зробити це - краще, ніж будь-який із відповідей, які я бачу.
користувач1258361

Відповіді:


320

Так, вміщує регістр. Ви можете використовувати java.util.regex.Pattern з прапором CASE_INSENSITIVE для відповідності нечутливості до регістру:

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

EDIT: Якщо 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, яка має дві великі літери для тієї самої великої форми).
Клітос Кіріако

266

Одна з проблем з відповіддю Дейва Л. полягає в тому, коли s2 містить розмітку регулярного вираження, наприклад\d , і т.д.

Ви хочете зателефонувати Pattern.quote () на s2:

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();

1
Гарний лов Метт. Мені цікаво дізнатись, який метод є більш ефективним - він містить малі регістри або ваше шаблонне рішення. Чи не використовується шаблон менш ефективний для одного порівняння, але більш ефективний для кількох порівнянь?
Аарон

41
Метод .toLowerCase (). Містить (), ймовірно, у більшості випадків буде швидшим. Я, мабуть, віддав перевагу цьому стилю і для меншої складності.
Метт Перепел

3
@AaronFerguson Так, дійсно, toLowerCase().contains()швидше. Я провів деякий аналіз швидкості, см моя відповідь на результати: stackoverflow.com/a/25379180/1705598
icza

2
@MattQuail не має сенсу бути швидшим, якщо він може бути неправильним. Наприклад, грецька сигма великої літери має дві малі форми (залежно від того, входить вона в кінці слова чи ні), і при спробі невідповідності підстрокових збігів підрядків, де підрядка закінчується сигмою, ви можете легко отримати неправильну результати.
Клітос Кіріако

Думаю, нам слід додати і Pattern.UNICODE_CASEпрапор. Чи можете ви підтвердити це?
Thariq Nugrohotomo

160

Можна використовувати

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

Бібліотека Apache Commons дуже корисна для подібних речей. І цей конкретний може бути кращим, ніж регулярні вирази, оскільки регулярний вираз завжди дорогий з точки зору продуктивності.


1
Хтось знає, чи поважає це місце?
Чарльз Вуд

12
@CharlesWood Він делегує String.regionMatches, який використовує перетворення символів, тому ні. Крім того, containsIgnoreCase("ß", "ss")повертається -1, що неправильно в кожній місцевості (німецький "гострий s" з великої літери використовує "ss".
maaartinus

Який був би правильний спосіб порівняння німецьких слів тоді? Здається, це одна мова, яка ускладнює кожен спосіб порівняння рядків: P
гризти

1
BTW: німецька мова була офіційно розширена з великої літери у 2017 році: de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F . На німецьких клавіатурах введіть Shift + Alt Gr + ß -> test: ẞ 😁
Kawu

119

Швидше виконання: використання String.regionMatches()

Використання regexp може бути відносно повільним. Це (повільно) не має значення, якщо ви просто хочете перевірити в одному випадку. Але якщо у вас є масив або колекція тисяч або сотень тисяч рядків, все може йти досить повільно.

Наведене нижче рішення не використовує ні регулярні вирази, ні toLowerCase() (що також повільно, оскільки створює інші рядки і просто викидає їх після перевірки).

Рішення ґрунтується на String.regionMatches () який, здається, невідомий. Він перевіряє, чи відповідають 2 Stringрегіони, але важливо, що він також має перевантаження із зручним 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 ms
  4. Regexp: 7180 мс
  5. Regexp з кешем 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");
    }

}

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().
ікза

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.
kroiz

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/…
Адріан Костер

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

3

Я зробив тест, знайшовши невідчутну до регістру відповідність струни. У мене вектор з 150 000 об'єктів, який містить рядок як одне поле і хотів знайти підмножину, яка відповідатиме рядку. Я спробував три методи:

  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)
 * */

0

Я не впевнений, що тут є вашим головним питанням, але так,.


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

В основному це метод, який займає дві нитки. Це повинна бути версія, що не враховує регістр містить (). Використовуючи метод містить, ви хочете дізнатися, чи міститься одна рядок в іншій.

Цей метод бере рядок "sub" і перевіряє, чи він дорівнює підрядках рядка контейнера, які за довжиною рівні "sub". Якщо ви подивитеся на forцикл, ви побачите, що він повторюється в підрядках (що є довжиною "під") над рядком контейнера.

Кожна ітерація перевіряє, чи відповідає підрядок рядка контейнера equalsIgnoreCaseпідпункту.


в основному це метод, який займає два рядки. це припущення, що це версія, що не враховує регістр. використовуючи метод містить, ви хочете перевірити, чи міститься одна рядок в іншій. цей метод бере рядок, який є "sub", і перевіряє, чи він дорівнює підрядним рядкам контейнера, що за довжиною дорівнює "sub". якщо ви подивитеся на цикл for, ви побачите, що він повторюється в підрядних рядках (що є довжиною "sub") над рядком контейнера. кожна ітерація перевіряє, чи є підрядний рядок контейнера рівнимзначенням підзарядки.
Seth

@Ви, мабуть, слід додати це до своєї відповіді.
Хлопець з капелюхом

2
Це найповільніший метод коли-небудь ... і також не вдається для німецької мови.
maaartinus

0

Якщо вам доведеться шукати рядок ASCII в іншій рядку ASCII, наприклад URL-адресі , ви знайдете моє рішення для кращого. Я перевірив метод ідзи на швидкість і ось результати:

  • Випадок 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));
}

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

    }

}

Дякуємо за цей фрагмент коду, який може надати деяку короткочасну допомогу. Правильне пояснення значно покращило б його довгострокове значення, показавши, чому це хороше рішення проблеми, і зробило б кориснішим майбутнім читачам інші подібні питання. Будь ласка , змініть свій відповідь , щоб додати деякі пояснення, в тому числі припущень , які ви зробили.
Toby Speight

0
"AbCd".toLowerCase().contains("abcD".toLowerCase())

2
Чи можете ви покращити свою відповідь, пояснивши, як ваш код вирішує проблему?
Ісука

1
Ця відповідь вже запропонована в багатьох інших, більш детальних відповідях на це питання, які надали інші. Я не думаю, що ця відповідь не служить жодній меті тут.
DaveyDaveDave

0

Ми можемо використовувати потік з будь-якимMatch та містить 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

або ви можете скористатися простим підходом і просто перетворити корпус рядка в регістр підрядки, а потім використовувати метод містить метод.


-1
String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());

-1

Ви можете просто зробити щось подібне:

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.