Как проверить, содержит ли строка другую строку без учета регистра в Java? - PullRequest
348 голосов
/ 17 сентября 2008

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

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

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

return s1.contains(s2);

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

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

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

Ответы [ 18 ]

300 голосов
/ 17 сентября 2008

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

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

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

247 голосов
/ 18 сентября 2008

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

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

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
134 голосов
/ 05 марта 2012

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

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

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

106 голосов
/ 19 августа 2014

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

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

Представленное ниже решение не использует ни регулярные выражения, ни 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 мс
  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

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


Код аналитического теста

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

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

}
18 голосов
/ 16 января 2012

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

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
    System.out.println("It's a match!");
}
16 голосов
/ 15 апреля 2010

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

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;

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

6 голосов
/ 20 октября 2012

Вы можете использовать регулярные выражения , и это работает:

boolean found = s1.matches("(?i).*" + s2+ ".*");
3 голосов
/ 27 мая 2014

Вот некоторые 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 голосов
/ 16 января 2014

Я сделал тест, чтобы найти регистр без учета совпадения строки. У меня есть Вектор из 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 мсек

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

1 голос
/ 13 декабря 2017
"AbCd".toLowerCase().contains("abcD".toLowerCase())
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...