«1. Обзор
В этом руководстве мы рассмотрим несколько способов проверки наличия в строке подстроки и сравним производительность каждого из них.
2. String.indexOf
Давайте сначала попробуем использовать метод String.indexOf. indexOf дает нам первую позицию, где найдена подстрока, или -1, если она вообще не найдена.
Когда мы ищем «Rhap», он возвращает 9:
Assert.assertEquals(9, "Bohemian Rhapsodyan".indexOf("Rhap"));
Когда мы ищем «rhap», он возвращает -1, потому что он чувствителен к регистру.
Assert.assertEquals(-1, "Bohemian Rhapsodyan".indexOf("rhap"));
Assert.assertEquals(9, "Bohemian Rhapsodyan".toLowerCase().indexOf("rhap"));
Также важно отметить, что если мы ищем подстроку «an», она вернет 6, потому что возвращает первое вхождение:
Assert.assertEquals(6, "Bohemian Rhapsodyan".indexOf("an"));
3. String.contains
Next , попробуем String.contains. contains будет искать подстроку по всей строке и вернет true, если она найдена, и false в противном случае.
В этом примере contains возвращает true, потому что найдено «Hey».
Assert.assertTrue("Hey Ho, let's go".contains("Hey"));
Если строка не найдена, contains возвращает false:
Assert.assertFalse("Hey Ho, let's go".contains("jey"));
В последнем примере «hey» не найдено, поскольку String.contains чувствителен к регистру.
Assert.assertFalse("Hey Ho, let's go".contains("hey"));
Assert.assertTrue("Hey Ho, let's go".toLowerCase().contains("hey"));
Интересным моментом является то, что функция contains внутренне вызывает indexOf, чтобы узнать, содержится подстрока или нет.
4. StringUtils.containsIgnoreCase
Наш третий подход будет использовать StringUtils#containsIgnoreCase из библиотеки Apache Commons Lang:
Assert.assertTrue(StringUtils.containsIgnoreCase("Runaway train", "train"));
Assert.assertTrue(StringUtils.containsIgnoreCase("Runaway train", "Train"));
Мы можем видеть, что он проверяет, содержится ли подстрока в строке, игнорируя дело. Вот почему containsIgnoreCase возвращает true, когда мы ищем «Trai», а также «trai» внутри «Runaway Train».
Этот подход не будет таким эффективным, как предыдущие подходы, так как для игнорирования случая требуется дополнительное время. containsIgnoreCase внутренне преобразует каждую букву в верхний регистр и сравнивает преобразованные буквы вместо исходных.
5. Использование шаблона
Наш последний подход будет использовать шаблон с регулярным выражением:
Pattern pattern = Pattern.compile("(?<!\\S)" + "road" + "(?!\\S)");
Мы можем заметить, что сначала нам нужно построить шаблон, затем нам нужно создать сопоставление, и наконец, мы можем проверить с помощью метода find, встречается ли подстрока или нет:
Matcher matcher = pattern.matcher("Hit the road Jack");
Assert.assertTrue(matcher.find());
Например, при первом выполнении find возвращается true, потому что слово «дорога» содержится внутри строка «Hit the road Jack», но когда мы пытаемся найти то же слово в строке «и больше не возвращайся», она возвращает false:
Matcher matcher = pattern.matcher("and don't you come back no more");
Assert.assertFalse(matcher.find());
6. Сравнение производительности ~~ ~ Мы будем использовать платформу микротестирования с открытым исходным кодом под названием Java Microbenchmark Harness (JMH), чтобы решить, какой метод является наиболее эффективным с точки зрения времени выполнения.
6.1. Настройка теста
Как и в каждом тесте JMH, у нас есть возможность написать метод настройки, чтобы иметь определенные вещи перед запуском наших тестов:
В методе настройки мы инициализируем поле сообщения. Мы будем использовать его в качестве исходного текста для наших различных реализаций поиска.
@Setup
public void setup() {
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " +
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " +
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris " +
"nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in " +
"reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " +
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " +
"mollit anim id est laborum";
pattern = Pattern.compile("(?<!\\S)" + "eiusmod" + "(?!\\S)");
}
Мы также инициализируем шаблон, чтобы использовать его позже в одном из наших тестов.
6.2. Тест String.indexOf
Наш первый тест будет использовать indexOf:
Мы будем искать, в какой позиции «eiusmod» присутствует в переменной сообщения.
@Benchmark
public int indexOf() {
return message.indexOf("eiusmod");
}
6.3. Тест String.contains
Наш второй тест будет использовать contains:
Мы попытаемся найти, содержит ли значение сообщения «eiusmod» — ту же подстроку, которая использовалась в предыдущем тесте.
@Benchmark
public boolean contains() {
return message.contains("eiusmod");
}
6.4. Тест StringUtils.containsIgnoreCase
Наш третий тест будет использовать StringUtils#containsIgnoreCase:
Как и в предыдущих тестах, мы будем искать подстроку в значении сообщения.
@Benchmark
public boolean containsStringUtilsIgnoreCase() {
return StringUtils.containsIgnoreCase(message, "eiusmod");
}
6.5. The Pattern Benchmark
И наш последний тест будет использовать Pattern:
Мы будем использовать шаблон, инициализированный в методе setup, чтобы создать Matcher и иметь возможность вызывать метод find, используя ту же подстроку, что и раньше. .
@Benchmark
public boolean searchWithPattern() {
return pattern.matcher(message).find();
}
6.6. Анализ результатов тестов
Важно отметить, что мы оцениваем результаты тестов в наносекундах.
После запуска нашего теста JMH мы можем увидеть среднее время, которое потребовалось каждому:
«contains: 14,736 нс indexOf: 14,200 нс containsStringUtilsIgnoreCase: 385,632 нс searchWithPattern: 1014,633 нс Метод
-
indexOf является наиболее эффективным, за ним следует метод contains. Имеет смысл, что метод contains занял больше времени, потому что внутри используется indexOf.
containsStringUtilsIgnoreCase занял больше времени по сравнению с предыдущими, потому что он нечувствителен к регистру.
searchWithPattern в среднем занял еще больше времени, чем последний, доказывая, что использование Patterns — наихудшая альтернатива для этой задачи.
7. Заключение
В этой статье мы рассмотрели различные способы поиска подстроки в строке. Мы также проверили производительность различных решений.
Как всегда, код доступен на GitHub.
«