Скажем, у меня две струны,
String s1 = "AbBaCca";
String s2 = "bac";
Я хочу выполнить проверку, чтобы убедиться, что s2 содержится в s1. Я могу сделать это с помощью:
return s1.contains(s2);
Я почти уверен, что contains() чувствителен к регистру, однако я не могу точно определить это, читая документацию. Если это так, то я полагаю, что моим лучшим методом будет что-то вроде:
return s1.toLowerCase().contains(s2.toLowerCase());
Помимо всего этого, есть ли другой (возможно, лучший) способ сделать это, не заботясь о чувствительности к регистру?
Ваше решение проще, чем любое из приведенных в ответах
Ответ, который ищем я и многие здесь, находится в вашем вопросе.
Ваш пример - самый простой, наиболее читаемый и, вероятно, лучший способ сделать это - лучше, чем любой из ответов, которые я вижу.




Да, содержит с учетом регистра. Вы можете использовать java.util.regex.Pattern с флагом CASE_INSENSITIVE для сопоставления без учета регистра:
Pattern.compile(Pattern.quote(wantedStr), Pattern.CASE_INSENSITIVE).matcher(source).find();
Обновлено: Если s2 содержит специальные символы регулярных выражений (которых много), важно сначала заключить их в кавычки. Я исправил свой ответ, так как это будет первое сообщение, которое увидят люди, но проголосуйте за Мэтта Куэла, поскольку он указал на это.
Как указано в документации для Pattern.CASE_INSENSITIVE, это работает только для символов ASCII (т.е. «Ä» не будет соответствовать «ä»). Для этого необходимо дополнительно указать флаг UNICODE_CASE.
является ли этот подход с использованием Pattern более производительным, чем s1.toLowerCase().contains(s2.toLowerCase())?
Первая попытка не сработала, затем я изменил s2 <-> s1, все работает.
@ GünayGültekin Это странно. Приведенный код возвращает true и выглядит правильным, но если я переключаю s2 и s1, он возвращает false. Не могли бы вы перепутать свои определения?
Требуется ли меньшая производительность, чем toLowerCase ()? Даже в длинных струнах? Если нет, зачем мне его использовать? Этот метод тоже «длинный» для написания. Для меня это выглядит менее читабельным.
@ user01 Я провел анализ скорости. Результаты см. В моем ответе (я также показал более быстрое решение): stackoverflow.com/a/25379180/1705598
Использование Pattern.LITERAL будет быстрее, чем Pattern.quote.
Было бы мне более понятно, что происходит, если бы у нас были лучшие имена переменных: Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find()
Правильность @ user01 важнее производительности, и использование toLowerCase даст потенциально неверные результаты (например, при сравнении определенного греческого текста, содержащего букву «сигма», который имеет две строчные формы для одной и той же прописной формы).
Хотя вы правы в том, что обычно лучше быть правильным, чем быстро, но это не всегда так. Я все еще надеюсь найти что-то быстрое и точное.
Вы можете просто преобразовать обе строки в нижний регистр следующим образом: string.toLowerCase (). Contains ("someExampleString" .toLowerCas e ()) ;.
Я не уверен, в чем ваш главный вопрос, но да, .contains чувствителен к регистру.
он спрашивает, как сопоставить строки с учетом регистра.
Одна проблема с ответ Дэйва Л. - когда s2 содержит разметку регулярных выражений, такую как \d и т. д.
Вы хотите вызвать Pattern.quote () на s2:
Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
Хороший улов, Мэтт. Мне любопытно узнать, какой метод более эффективен - строчные буквы или ваше шаблонное решение. Разве использование шаблона не менее эффективно для одиночного сравнения, но более эффективно для множественных сравнений?
Метод .toLowerCase (). Contains (), вероятно, будет быстрее в большинстве случаев. Я бы, наверное, предпочел этот стиль и для меньшей сложности.
@AaronFerguson Да, действительно, toLowerCase().contains() быстрее. Я провел некоторый анализ скорости, результаты см. В моем ответе: stackoverflow.com/a/25379180/1705598
@MattQuail нет смысла быть быстрее, если он может быть неправильным. Например, греческая заглавная сигма имеет две строчные формы (в зависимости от того, стоит ли она в конце слова или нет), и при попытке сопоставить подстроку без учета регистра, где подстрока заканчивается сигмой, вы можете легко получить неверный результат. полученные результаты.
Думаю, нам тоже стоит добавить флаг Pattern.UNICODE_CASE. Не могли бы вы это подтвердить?
Да, это достижимо:
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;
Этот код вернет строку "ИСТИНА!" как выяснилось, что ваши персонажи содержались.
Большой недостаток использования toLowerCase () заключается в том, что результат зависит от текущего Locale. См .: javapapers.com/core-java/…
Вопрос на самом деле содержит лучшее решение, поскольку это не подходит для s2, не использующего строчные буквы. Не говоря уже о таких деталях, как то, что этот не компилируется, и если бы он компилировался, он вернул бы строку.
String x = "abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());
Более простой способ сделать это (не беспокоясь о сопоставлении с образцом) - преобразовать оба String в нижний регистр:
String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
System.out.println("It's a match!");
}
Регистр символов зависит от языка, что означает, что он будет работать на вашем компьютере, но не будет работать для клиента :). см. комментарий @Adriaan Koster.
@kroiz, это зависит от того, откуда взялась строка. Сравнение "foobar" и "FOO" всегда будет совпадать, однако, если вы сравниваете вводимую пользователем информацию или специфичный для языка контент, то вы правы - разработчик должен быть осторожен.
Вы можете использовать
org.apache.commons.lang3.StringUtils.containsIgnoreCase("AbBaCca", "bac");
Библиотека Apache Commons очень полезна для такого рода вещей. И это конкретное выражение может быть лучше, чем регулярные выражения, поскольку регулярное выражение всегда дорого с точки зрения производительности.
Кто-нибудь знает, уважает ли это локаль?
@CharlesWood Он делегирует String.regionMatches, который использует посимвольные преобразования, поэтому нет. Более того, containsIgnoreCase("ß", "ss") возвращает -1, что неверно во всех региональных стандартах (немецкое «диез» с заглавной буквы превращается в «сс».
Какой же тогда способ сравнения немецких слов был бы правильным? Кажется, это один язык, который усложняет все способы сравнения строк: P
Кстати: немецкий язык был официально расширен с заглавной буквы ß в 2017 году: de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F. На немецкой клавиатуре введите Shift + Alt Gr + ß -> test: ẞ ?
Точно так же у вас может быть проблема с заглавными буквами / нижним регистром букв с точками и без точек i <-> İ и ı <-> I на турецком языке (например, в Стамбуле) по сравнению со многими нетюркскими языками, использующими на самом деле слегка отличные гласные для прописных / строчных букв. Я <-> я.
Вы можете использовать обычные выражения, и он работает:
boolean found = s1.matches("(?i).*" + s2+ ".*");
Я провел тест, обнаружив совпадение строки без учета регистра. У меня есть вектор из 150 000 объектов, все со строкой как одно поле, и я хотел найти подмножество, соответствующее строке. Я попробовал три метода:
Преобразовать все в нижний регистр
for (SongInformation song: songs) {
if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) {
...
}
}
Используйте метод String Match ()
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 мсек.
Регулярное выражение выглядит самым быстрым для этого варианта использования.
Хорошо, что вы поставили тайминг результаты. Все говорят, насколько медленное регулярное выражение, но на самом деле оно очень быстрое, если вам нужно только один раз скомпилировать регулярное выражение.
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 для подпрограммы.
в основном это метод, который требует двух строк. Предполагается, что это не чувствительная к регистру версия contains (). при использовании метода contains вы хотите видеть, содержится ли одна строка в другой. этот метод берет строку "sub" и проверяет, равна ли она подстрокам строки контейнера, которые равны по длине "sub". если вы посмотрите на цикл for, вы увидите, что он выполняет итерацию в подстроках (которые являются длиной «подстроки») по строке контейнера. каждая итерация проверяет, совпадает ли подстрока строки контейнера с регистром подстроки.
@ Вы, вероятно, должны добавить это к своему ответу.
Это самый медленный метод из когда-либо существовавших ... и он также не подходит для немецкого языка.
Вот несколько дружественных к 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();
}
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 методов.
String.contains().String.contains() с предварительно кэшированной подстрокой в нижнем регистре. Это решение уже не так гибко, потому что тестирует предопределенную подстроку.Pattern.compile().matcher().find() ...)Pattern. Это решение уже не так гибко, потому что тестирует предопределенную подстроку.Результаты (вызвав метод 10 миллионов раз):
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");
}
}
+1, но обратите внимание, что он не работает для ß (немецкое острое S; используется для SS с заглавной буквы), а также для некоторых других символов (см. Исходный код String.regionMatches, который пытается оба преобразования).
Вы всегда тестируете одни и те же строки, что на самом деле не совсем справедливое сравнение. «Я» всегда находится посередине, что может иметь или не иметь значения для различных методов поиска. Лучше было бы генерировать случайные строки, а также сообщать о скорости, когда подстрока отсутствует.
Это похоже на метод Apache StringUtils: grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/…
@ alain.janinm Я не вижу сходства. Единственное, что кажется «близким» к StringUtils.containsIgnoreCase(), это то, что и мое решение, и решение Apache используют метод regionMatches() (в цикле), но даже это не то же самое, что я называю String.regionMatches(), а Apache вызывает CharSequenceUtils.regionMatches().
@icza CharSequenceUtils.regionMatches на самом деле просто вызывает String.regionMatches. В любом случае, я хотел дать информацию, что если кто-то уже использует StringUtils lib, он может просто вызвать его, потому что это кажется эффективным способом, как вы доказываете это своим тестом. Если бы я не использовал Apache lib, я бы определенно использовал ваш метод;)
Умная. Есть ли какая-то конкретная причина производительности, по которой вы начинаете поиск src в конце what? Похоже, что наиболее распространенный вариант использования этой функции - поисковый фильтр, и в этом случае есть вероятность, что what будет в начале src.
@MartinDevillers Я начинаю поиск в конце, чтобы петля могла идти вниз. И я иду вниз, потому что таким образом условие цикла будет короче и эффективнее (по сравнению с 0), а также так, что не потребуется временная переменная для хранения конечной / последней позиции.
ОК, спасибо за ответ! Я не совсем уверен, что эти преимущества перевешивают упомянутые мной недостатки, но, по крайней мере, это ясно.
Готов поспорить, что значительная часть вашего ускорения - это односимвольный тест. Но если вы собираетесь потратить время и силы на это ускорение, вам следует просто использовать вместо этого Бойера – Мура.
Вам следует попробовать сравнить движение вверх и движение вниз. Я подозреваю, что соображение производительности, которое поднял Мартин, более важно, чем сравнение с нулем. Я обычно пишу циклы for, инициализирующие две переменные, i, начиная с 0 и двигаясь вверх, и n, устанавливая максимальное значение, чтобы оно сохранялось локально и не извлекалось повторно и не вычислялось откуда бы то ни было. Содержит большинство тех же преимуществ производительности, что и при запуске с max и при переходе к 0 ... ваш стек на несколько байтов длиннее, потому что одна дополнительная локальная переменная - это все.
Очень тщательно. Я хотел бы сделать вывод, что все они находятся в разумном диапазоне друг от друга, за исключением нескомпилированного регулярного выражения, поэтому любой метод, который вы считаете наиболее легким для запоминания и реализации в то время, достаточно хорош, но избегайте стандартного регулярного выражения, и что есть быстрое решение, если вы определяете это как узкое место в производительности.
В моих тестах это было медленным, очень медленно сравнивать взаимодействие по списку на Android с использованием SearchView с реализацией Filter на моем Adapter.
@icza, можете ли вы добавить apache StringUtils.containsIngnoreCase в свой тестовый пример? Думаю, здесь людям легче понять, что твое быстрее.
Чем это лучше, чем упомянутая одна строка кода, в которой вы конвертируете обе строки в нижний регистр перед сравнением?
@Mugen Это решение не преобразует строки в нижний регистр.
Если вам нужно искать строку ASCII в другой строке ASCII, такой как URL, вы найдете мое решение лучше. Я тестировал метод icza и мой на скорость и вот результаты:
Код:
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));
}
Вы можете просто сделать что-то вроде этого:
String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
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");
}
}
}
Спасибо за этот фрагмент кода, который может предоставить некоторую краткосрочную помощь. Правильное объяснение значительно улучшит его долгосрочной ценности, показывая Почему, это хорошее решение проблемы, которое сделает его более полезным для будущих читателей с другими подобными вопросами. Пожалуйста, редактировать свой ответ, чтобы добавить некоторые пояснения, включая сделанные вами предположения.
"AbCd".toLowerCase().contains("abcD".toLowerCase())
Можете ли вы улучшить свой ответ, объяснив, как ваш код решает проблему?
Этот ответ уже предлагался во многих других, более подробных ответах на этот вопрос, которые были предоставлены другими. Я не думаю, что этот ответ здесь служит какой-либо цели.
Мы можем использовать поток с 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;
}
}
Существует простой и краткий способ использования флага регулярного выражения (без учета регистра {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)
* */
или вы можете использовать простой подход и просто преобразовать регистр строки в регистр подстроки, а затем использовать метод contains.
Думаю, вы ответили на свой вопрос. Я не думаю, что какое-либо из приведенных ниже решений лучше этого. Но они определенно медленнее.