1. Обзор
В этом руководстве мы рассмотрим несколько способов проверки наличия в строке
подстроки и сравним производительность каждого из них.
2. Строка.indexOf
Давайте сначала попробуем использовать метод String.indexOf
. indexOf
дает нам первую позицию, где найдена подстрока, или -1, если она вообще не найдена.
Когда мы ищем «Rhap», он возвращает 9:
Assert.assertEquals(9, "Bohemian Rhapsodyan".indexOf("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
. 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"));
В последнем примере «привет» не найдено, потому что 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"));
Мы можем видеть, что он проверяет, содержится ли подстрока
в String
, игнорируя регистр . Вот почему containsIgnoreCase
возвращает true
, когда мы ищем «Trai», а также «trai» внутри «Runaway Train».
Этот подход не будет таким эффективным, как предыдущие подходы , так как для игнорирования случая требуется дополнительное время. containsIgnoreCase
внутренне преобразует каждую букву в верхний регистр и сравнивает преобразованные буквы вместо исходных.
5. Использование шаблона
Наш последний подход будет использовать Pattern
с регулярным выражением :
Pattern pattern = Pattern.compile("(?<!\\S)" + "road" + "(?!\\S)");
Мы можем заметить, что сначала нам нужно построить Pattern
, затем нам нужно создать Matcher
, и, наконец, мы можем проверить с помощью метода 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
:
@Benchmark
public int indexOf() {
return message.indexOf("eiusmod");
}
Мы будем искать, в какой позиции «eiusmod» присутствует в переменной сообщения .
6.3. Эталон String.contains
_
Наш второй тест будет использовать contains
:
@Benchmark
public boolean contains() {
return message.contains("eiusmod");
}
Мы попытаемся найти, содержит
ли значение сообщения
«eiusmod», ту же подстроку
, которая использовалась в предыдущем тесте.
6.4. Тест StringUtils.containsIgnoreCase
_
Наш третий тест будет использовать StringUtils#
containsIgnoreCase
:
@Benchmark
public boolean containsStringUtilsIgnoreCase() {
return StringUtils.containsIgnoreCase(message, "eiusmod");
}
Как и в предыдущих тестах, мы будем искать подстроку
в значении сообщения .
6.5. Эталон шаблона
_
И наш последний тест будет использовать Pattern
:
@Benchmark
public boolean searchWithPattern() {
return pattern.matcher(message).find();
}
Мы будем использовать шаблон, инициализированный в методе setup
, чтобы создать Matcher
и иметь возможность вызывать метод find
, используя ту же подстроку, что и раньше.
6.6. Анализ результатов бенчмарков
Важно отметить, что мы оцениваем результаты тестов в наносекундах .
После запуска нашего теста JMH мы можем увидеть среднее время, которое потребовалось каждому:
содержит
: 14,736 нсindexOf
: 14.200 нссодержитStringUtilsIgnoreCase
: 385,632 нсsearchWithPattern
: 1014,633 нс
Метод indexOf
является наиболее эффективным, за ним следует метод contains
. Имеет смысл, что метод contains
занял больше времени, потому что внутри используется indexOf
.
СодержитStringUtilsIgnoreCase
потребовалось больше времени по сравнению с предыдущими, потому что он нечувствителен к регистру.
searchWithPattern
в среднем занял еще больше времени, чем предыдущий, доказывая, что использование Pattern
s — наихудшая альтернатива для этой задачи.
7. Заключение
В этой статье мы рассмотрели различные способы поиска подстроки в строке.
Мы также проверили производительность различных решений.
Как всегда, код доступен на GitHub .