«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.

«