Как проверить, содержит ли строка другую строку без учета регистра в Java?
Скажем, у меня есть две строки,
String s1 = "AbBaCca";
String s2 = "bac";
Я хочу выполнить проверку, возвращающую s2
содержится в s1
, Я могу сделать это с:
return s1.contains(s2);
Я уверен, что contains()
чувствителен к регистру, однако я не могу определить это наверняка, прочитав документацию. Если это так, то я полагаю, что мой лучший метод будет что-то вроде:
return s1.toLowerCase().contains(s2.toLowerCase());
Помимо всего этого, есть ли другой (возможно, лучший) способ сделать это, не заботясь о чувствительности к регистру?
22 ответа
Да, содержит регистр. Вы можете использовать java.util.regex.Pattern с флагом CASE_INSENSITIVE для сопоставления без учета регистра:
Pattern.compile(Pattern.quote(wantedStr), Pattern.CASE_INSENSITIVE).matcher(source).find();
РЕДАКТИРОВАТЬ: Если s2 содержит специальные символы регулярных выражений (из которых их много), важно сначала процитировать его. Я исправил свой ответ, так как это первый, который увидят люди, но проголосуйте за Мэтта Квила, так как он указал на это.
Одна проблема с ответом Дэйва Л. - это когда s2 содержит разметку регулярного выражения, такую как \d
, так далее.
Вы хотите вызвать Pattern.quote() на s2:
Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
Ты можешь использовать
org.apache.commons.lang3.StringUtils.containsIgnoreCase("AbBaCca", "bac");
Библиотека Apache Commons очень полезна для такого рода вещей. И этот конкретный может быть лучше, чем регулярные выражения, так как регулярное выражение всегда дорого с точки зрения производительности.
Более быстрое внедрение: использование 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 методов.
- Наш метод содержит IgnoreCase().
- Путем преобразования обеих строк в нижний регистр и вызова
String.contains()
, - Преобразование исходной строки в нижний регистр и вызов
String.contains()
с предварительно кэшированной подстрокой в нижнем регистре. Это решение уже не так гибко, потому что оно тестирует предстроку подстроки. - Используя регулярное выражение (принятый ответ
Pattern.compile().matcher().find()
...) - Использование регулярного выражения, но с предварительно созданным и кэшированным
Pattern
, Это решение уже не так гибко, потому что оно тестирует предопределенную подстроку.
Результаты (вызвав метод 10 миллионов раз):
- Наш метод: 670 мс
- 2x toLowerCase() и содержит (): 2829 мс
- 1x toLowerCase() и содержит () с кэшированной подстрокой: 2446 мс
- Регулярное выражение: 7180 мс
- Регулярное выражение с кэшированием
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");
}
}
Более простой способ сделать это (не беспокоясь о сопоставлении с образцом) будет конвертировать оба String
с в нижнем регистре:
String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
System.out.println("It's a match!");
}
Да, это достижимо
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;
Этот код вернет строку "ИСТИНА!" как выяснилось, что ваши персонажи содержались.
Вы можете использовать регулярные выражения, и это работает:
boolean found = s1.matches("(?i).*" + s2+ ".*");
Вот некоторые 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();
}
Существует простой и краткий способ использования флага регулярных выражений (без учета регистра {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)
* */
Я сделал тест, чтобы найти регистр без учета совпадения строки. У меня есть Вектор из 150000 объектов со Строкой в одном поле, и я хотел найти подмножество, соответствующее строке. Я попробовал три метода:
Конвертировать все в нижний регистр
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 мсек
Регулярное выражение выглядит самым быстрым для этого варианта использования.
Один из способов сделать это - преобразовать обе строки в нижний или верхний регистр с помощью методов и теста toLowerCase () или toUpperCase().
public class Sample {
public static void main(String args[]){
String str = "Hello Welcome to insensitive Container";
String test = "Java Testing";
Boolean bool = str.toLowerCase().contains(test.toLowerCase());
System.out.println(bool);
}
}
Вот еще один способ сопоставления без учета регистра с использованием java.util.regex.Pattern с флагом CASE_INSENSITIVE.
Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();
Я не уверен, что ваш главный вопрос здесь, но да, .contains учитывает регистр.
Если вам придется искать строку 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));
}
Быстрый обходной путь:
if (yourstring.toLowerCase().contains("your string lower case")){
}
Или вы можете использовать простой подход и просто преобразовать регистр строки в регистр подстроки, а затем использовать метод contains.
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");
}
}
}
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 вы хотите увидеть, содержится ли одна строка в другой.
Этот метод берет строку, которая является "sub", и проверяет, равна ли она подстрокам строки контейнера, длина которых равна "sub". Если вы посмотрите на for
В цикле вы увидите, что он перебирает подстроки (длина "sub") над строкой контейнера.
Каждая итерация проверяет, является ли подстрока строки контейнера equalsIgnoreCase
к суб.
Вы можете просто сделать что-то вроде этого:
String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
Мы можем использовать поток с 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;
}
}
String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());
Еще один простой в использовании метод поиска строки внутри строки - это 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