Обзор производительности регулярных выражений в Java

Обзор производительности регулярных выражений в Java

1. обзор

В этом кратком руководстве мы покажем, как работает механизм сопоставления с образцом. Мы также представим различные способы оптимизацииregular expressions в Java.

Для введения в использованиеregular expressions см.to this article here.

2. Механизм сопоставления с образцом

В пакетеjava.util.regex используется механизм сопоставления с образцом, называемыйNondeterministic Finite Automaton (NFA). It’s considered nondeterministic because while trying to match a regular expression on a given string, each character in the input might be checked several times against different parts of the regular expression..

В фоновом режиме упомянутый выше движок используетbacktracking. Этот общий алгоритм пытается исчерпать все возможности, пока не объявит о неудаче. Рассмотрим следующий пример, чтобы лучше понятьNFA:

"tra(vel|ce|de)m"

При вводеString «travel» механизм сначала будет искать «tra» и немедленно его находить.

После этого он попытается сопоставить «vel», начиная с четвертого символа. Это будет соответствовать, поэтому он будет идти вперед и пытаться сопоставить «m».

Это не будет совпадать, и по этой причине он вернется к четвертому символу и выполнит поиск «ce». Опять же, это не будет совпадать, поэтому он снова вернется к четвертой позиции и попробует с "de". Эта строка также не будет соответствовать, поэтому она вернется ко второму символу во входной строке и попытается найти другой «tra».

При последнем сбое алгоритм вернет сбой.

В последнем простом примере движку приходилось несколько раз возвращаться, пытаясь сопоставить вводString с регулярным выражением. Из-за этогоit’s important to minimize the amount of backtracking that it does.

3. Способы оптимизацииRegular Expressions

3.1. Избегайте перекомпиляции

Регулярные выражения в Java компилируются во внутреннюю структуру данных. Эта компиляция занимает много времени.

Каждый раз, когда мы вызываем методString.matches(String regex) , указанное регулярное выражение перекомпилируется:

if (input.matches(regexPattern)) {
    // do something
}

Как мы видим, каждый раз, когда условие оценивается, выражение регулярного выражения компилируется.

Для оптимизации можно сначала скомпилировать шаблон, а затем создатьMatcher, чтобы найти совпадения в значении:

Pattern pattern = Pattern.compile(regexPattern);
for(String value : values) {
    Matcher matcher = pattern.matcher(value);
    if (matcher.matches()) {
        // do something
    }
}

Альтернативой указанной выше оптимизации является использование того же экземпляраMatcher с его методомreset():

Pattern pattern = Pattern.compile(regexPattern);
Matcher matcher = pattern.matcher("");
for(String value : values) {
    matcher.reset(value);
    if (matcher.matches()) {
      // do something
    }
}

Из-за того, чтоMatcher не является потокобезопасным, мы должны быть осторожны с использованием этого варианта. Это может быть опасно в многопоточных сценариях.

Подводя итог, можно сказать, что в любой ситуации, когда мы уверены, что в любой момент времени есть только один пользовательMatcher, можно повторно использовать его сreset. В остальном достаточно использовать предварительно скомпилированный код.

3.2. Работа с чередованием

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

Кроме того, мы должны извлечь общие закономерности между ними. Это не одно и то же:

(travel | trade | trace)

чем:

tra(vel | de | ce)

Последнее быстрее, потому чтоNFA будет пытаться сопоставить «tra» и не будет пробовать ни одну из альтернатив, если не найдет ее.

3.3. Захват групп

Каждый раз, когда мы захватываем группы, мы налагаем мелкий штраф.

Если нам не нужно захватывать текст внутри группы, мы должны рассмотреть возможность использования групп без захвата. Вместо «(M)» используйте «(?:M)».

4. Заключение

В этой быстрой статье мы вкратце рассмотрели, как работаетNFA. Затем мы приступили к изучению того, как оптимизировать производительность наших регулярных выражений, предварительно скомпилировав наши шаблоны и повторно используяMatcher.

Наконец, мы указали на несколько соображений, которые следует учитывать при работе с чередованиями и группами.

Как обычно, полный исходный код можно найтиover on GitHub.