Какой самый быстрый способ поиска подстроки в Java?

#java #regex #string #algorithm #substring

#java #регулярное выражение #строка #алгоритм #подстрока

Вопрос:

Я хочу понять проблемы с производительностью, которые могут возникнуть при выполнении поиска по подстроке в Java. Я знаю два встроенных метода поиска подстроки в Java.

1. String.indexOf()

Насколько я понимаю, этот метод использует алгоритм перебора поиска подстроки, таким образом, его сложность равна O (nm), где n и m — длины строки и шаблона.

2. Используйте шаблон и средство сопоставления

Я ничего не знаю о том, как реализованы алгоритмы регулярных выражений и об их сложности.

Итак, вопросы:

1) Какой из этих методов предпочтительнее с точки зрения производительности?

2) В чем сложность поиска по регулярному выражению? Зависит ли это от самого регулярного выражения?

Комментарии:

1. Aho-Corasick — лучший выбор, если вас действительно беспокоит скорость

2. Доказано, что оптимизация по Бойеру-Муру в худшем случае выполняется за линейное время. Конечно, такого рода … противоречит цели вопроса с точки зрения того, что представлено. Вам нужен самый быстрый способ поиска подстроки с использованием только этих инструментов? Какие типы подстрок вы ищете? Не могли бы вы привести примеры ввода и ожидаемого результата?

3. Сложность поиска по регулярному выражению сильно варьируется в зависимости от того, что сопоставляется. В большинстве случаев оно совпадает очень быстро, но из-за обратного отслеживания на неудачное совпадение уходит много времени.

4. Я бы выбрал indexOf() вместо регулярного выражения, когда это возможно.

5. Причина indexOf использования этого алгоритма в том, что в большинстве случаев он самый быстрый на практике, даже если теоретически другие алгоритмы могут иметь лучшую асимптотику.

Ответ №1:

Честно говоря, если вас волнует производительность в наихудшем случае, используйте JNI в машинном коде, который вызывает strstr функцию вашей стандартной библиотеки. Хорошо реализованный strstr , как и в последних версиях glibc, имеет линейное время выполнения в наихудшем случае и постоянное использование пространства в наихудшем случае. Я полагаю, что glibc strstr также может выполнять длинные переходы по тексту, подобные Boyer-Moore. Стандартные библиотеки C поддерживаются людьми, которые знают, как писать и поддерживать хорошие библиотеки общего назначения, и практикуются в своем ремесле. Этого нельзя сказать о стандартной библиотеке классов Java.

Вам нужно будет превратить строку Java UTF-16 во что-то подходящее для strstr , например, строку UTF-8. Вам также придется корректно обрабатывать встроенные нулевые байты в строке UTF-8. Кроме этого, вы будете пользоваться преимуществами хорошо написанной и поддерживаемой библиотеки.

Java выполняет поиск по регулярным выражениям (для данного конкретного случая) с использованием поиска по строке Бойера-Мура, взломанного в наивной реализации регулярных выражений. Компиляция a Pattern только с вашей строкой приведет к Matcher относительно хорошей производительности. Однако обратите внимание, что это НЕ распространяется ни на что, кроме поиска строк с помощью библиотеки регулярных выражений; вы по-прежнему придерживаетесь наивной реализации регулярных выражений, которая выполняет обратный поиск, если вы вводите в нее нетривиальное регулярное выражение.

В качестве доказательства того, почему вы не должны использовать Java regex для реальных регулярных выражений, я представляю вам следующее:

 public class regex {
  public static void main(String[] args) throws Exception {
    String haystack = "ab";
    String needle = "abab?.*";
    for (int i = 0; i < 7; i  ) haystack = haystack   haystack;
    for (int i = 0; i < 4; i  ) needle = needle   needle;
    System.out.println(haystack.length()   " "   needle.length());
    long before = System.currentTimeMillis();
    System.out.println(Pattern.matches(needle, haystack));
    long after = System.currentTimeMillis(); // long after indeed...
    System.out.println(after - before);
  }
}
  

Это поиск в 256-символьном стоге сена регулярного выражения needle (это честное регулярное выражение, о котором вы узнали в классе compilers) из 112 символов. На моей машине для завершения требуется около 24 секунд.

Комментарии:

1. @Unihedron: На самом деле этого не произойдет. Возьмите любую книгу по вводным компиляторам и прочитайте о том, как реализовать регулярные выражения. Или посмотрите на реализацию регулярных выражений Расса Кокса на swtch.com /~rsc/regexp .

2. Знаете какие-нибудь движки регулярных выражений получше? Perl 5.10 занимает на 40% больше времени, чем Java 1.8.

3. @laune: На странице Расса Кокса есть указатели на четыре реализации. Я лично использовал библиотеку Russ re2. Вы можете убедиться, что это работает правильно, на примере, который я привел.

4. тмыклебу, я просто не могу понять из твоего ответа — каким способом быстрее искать точное соответствие подстроки: регулярным выражением или методом перебора или каким-то алгоритмом вроде KMP или Бойера-Мура, хорошо реализованным?

5. Я полагаю, что No1 — это какой-то алгоритм, ок, который не является вариантом 2, если я не хочу ничего кодировать самостоятельно?