Вопрос-ответ

How to check if a String contains another String in a case insensitive manner in Java?

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

Допустим, у меня есть две строки,

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

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

return s1.contains(s2);

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

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

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

Переведено автоматически
Ответ 1

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

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

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

Ответ 2

Одна из проблем с ответом Дейва Л. заключается в том, что 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()

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

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

  2. Путем преобразования обеих строк в нижний регистр и вызова String.contains().

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

  4. Используя регулярное выражение (принятый ответ Pattern.compile().matcher().find() ...)

  5. Используя регулярное выражение, но с предварительно созданным и кэшированнымPattern. Это решение уже не такое гибкое, потому что оно проверяет предопределенную подстроку.

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


  1. Наш метод: 670 мс

  2. 2 раза в нижнем регистре() и содержит(): 2829 мс

  3. 1x toLowerCase() и contains() с кэшированной подстрокой: 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");
}

}
java string