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

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

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

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

return s1.contains(s2);

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

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

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

Ответ 1

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

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

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

Ответ 2

Одна проблема с ответом Dave L. заключается в том, что s2 содержит разметку регулярных выражений, такую ​​как \d и т.д.

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

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

Ответ 3

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

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

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

Ответ 4

Более быстрая реализация: использование 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 методов.

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

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

  • Наш метод: 670 мс
  • 2x toLowerCase() и содержит(): 2829 мс
  • 1x toLowerCase() и содержит() с кешированной подстрокой: 2446 мс
  • Regexp: 7180 мс
  • 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");
    }

}

Ответ 5

Простейший способ сделать это (не беспокоясь о совпадении с образцом) будет преобразовывать как String в нижний регистр:

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
    System.out.println("It a match!");
}

Ответ 6

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

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;

Этот код вернет строку "TRUE!" поскольку он обнаружил, что ваши персонажи содержались.

Ответ 8

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

Ответ 9

Я проверил тест, не зависящий от регистра, совпадающий с строкой. У меня есть вектор 150 000 объектов со строкой как одно поле и хотел найти подмножество, которое соответствовало строке. Я пробовал три метода:

  • Преобразуйте все в нижний регистр

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

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

    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 мс

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

Ответ 10

Вот некоторые 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();
}

Ответ 11

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

Ответ 12

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

Ответ 13

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(). При использовании метода contains вы хотите увидеть, содержится ли одна строка в другой.

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

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

Ответ 14

Если вам нужно найти строку 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));
}

Ответ 15

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

    }

}

Ответ 16

Мы можем использовать поток с 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 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;
    }

}

Ответ 17

Существует простой и краткий способ использования флага регулярных выражений (без учета регистра {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)
 * */

Ответ 18

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

Ответ 19

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

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);

Ответ 20

Другим простым в использовании методом поиска строки внутри строки является STRING.INDEXOF()

  String str = new String("Welcome");
  System.out.print("Found Index :" );
  System.out.println(str.indexOf( 'o' ));

Найдено: 4

www.tutorialspoint.com/java/java_string_indexof.htm