Перейти к основному содержимому

Проверить, содержит ли строка подстроку

· 5 мин. чтения

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 .