java.util.regex - важность Pattern.compile ()?

118

В чем важность Pattern.compile()метода?
Почему мне нужно скомпилировать строку регулярного выражения перед получением Matcherобъекта?

Например :

String regex = "((\\S+)\\s*some\\s*";

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);
Sidharth
источник
2
Что ж, важность почти НЕТ, если реализация (например, в JDK 1.7) - это просто КОРОТКИЙ ПЕРЕХОД к новому шаблону (регулярное выражение, 0); Тем не менее, НАСТОЯЩЕЕ значение не в самом статическом методе, а в создании и возврате нового шаблона, который можно сохранить для последующего использования. Возможно, есть другие реализации, в которых статический метод берет новый маршрут и кэширует объекты Pattern, и это было бы реальным случаем важности Pattern.compile ()!
marcolopes 06
Ответы подчеркивают важность разделения шаблонов и сопоставления классов (что, вероятно, и задается вопросом), но никто не отвечает, почему мы не можем просто использовать конструктор new Pattern(regex)вместо статической функции компиляции. комментарий marcolopes на месте.
kon

Ответы:

144

compile()Метод всегда вызывается в какой - то момент; это единственный способ создать объект Pattern. Итак, вопрос в том, почему вы должны называть это явно ? Одна из причин заключается в том, что вам нужна ссылка на объект Matcher, чтобы вы могли использовать его методы, например, group(int)для получения содержимого групп захвата. Единственный способ получить объект Matcher - использовать метод объекта Pattern matcher(), а единственный способ получить объект Pattern - использовать этот compile()метод. Затем есть find()метод, который, в отличие от него matches(), не дублируется в классах String или Pattern.

Другая причина - избегать создания одного и того же объекта Pattern снова и снова. Каждый раз, когда вы используете один из методов на основе регулярных выражений в String (или статический matches()метод в Pattern), он создает новый Pattern и новый Matcher. Итак, этот фрагмент кода:

for (String s : myStringList) {
    if ( s.matches("\\d+") ) {
        doSomething();
    }
}

... в точности эквивалентно этому:

for (String s : myStringList) {
    if ( Pattern.compile("\\d+").matcher(s).matches() ) {
        doSomething();
    }
}

Очевидно, это делает много ненужной работы. Фактически, на компиляцию регулярного выражения и создание экземпляра объекта Pattern может уйти больше времени, чем на фактическое сопоставление. Поэтому обычно имеет смысл вытащить этот шаг из цикла. Вы также можете создать Matcher заранее, хотя они и далеко не такие дорогие:

Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("");
for (String s : myStringList) {
    if ( m.reset(s).matches() ) {
        doSomething();
    }
}

Если вы знакомы с регулярными выражениями .NET, вам может быть интересно, compile()связан ли метод Java с RegexOptions.Compiledмодификатором .NET ; ответ - нет. Pattern.compile()Метод Java просто эквивалентен конструктору Regex в .NET. Когда вы указываете Compiledопцию:

Regex r = new Regex(@"\d+", RegexOptions.Compiled); 

... он компилирует регулярное выражение непосредственно в байтовый код CIL, что позволяет ему работать намного быстрее, но со значительными затратами на предварительную обработку и использование памяти - думайте об этом как о стероидах для регулярных выражений. Java не имеет эквивалента; нет никакой разницы между шаблоном, который создается за кулисами, String#matches(String)и шаблоном, который вы создаете явно Pattern#compile(String).

(РЕДАКТИРОВАТЬ: я изначально сказал, что все объекты .NET Regex кэшируются, что неверно. Начиная с .NET 2.0, автоматическое кеширование происходит только со статическими методами, например Regex.Matches(), а не при прямом вызове конструктора Regex. Ref )

Алан Мур
источник
1
Тем не менее, это не объясняет важность такого TRIVIAL метода для класса Pattern! Я всегда предполагал, что статический метод Pattern.compile - это гораздо больше, чем просто SHORTCUT для нового Pattern (regex, 0); Я ожидал КЭШ скомпилированных паттернов ... я ошибался. Может, создание кеша дороже, чем создание новых паттернов ??!
marcolopes 06
9
Обратите внимание, что класс Matcher не является потокобезопасным и не должен использоваться в разных потоках. С другой стороны, Pattern.compile () есть.
gswierczynski 06
1
TLDR; «... [Pattern.compile (...)] компилирует регулярное выражение непосредственно в байт-код CIL, что позволяет ему работать намного быстрее, но со значительными затратами на предварительную обработку и использование памяти»
sean.boyer
3
Хотя это правда, что сопоставители не так дороги, как Pattern.compile, я сделал некоторые метрики в сценарии, в котором происходили тысячи совпадений регулярных выражений, и была дополнительная, очень значительная экономия, создав сопоставление заранее и повторно используя его через сопоставление. .сброс(). Избегание создания новых объектов в куче методами, вызываемыми тысячи раз, обычно намного легче для ЦП, памяти и, следовательно, для GC.
Volksman
@Volksman, это небезопасный общий совет, потому что объекты Matcher не являются потокобезопасными. Это тоже не имеет отношения к вопросу. Но да, вы могли бы resetиспользовать объект Matcher, который когда-либо использовался только одним потоком за раз, чтобы уменьшить выделение.
AndrewF
40

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

Томас Юнг
источник
7
Кроме того, во время компиляции вы можете указать такие флаги, как case_insensitive, dot_all и т. Д., Передав дополнительный параметр flags
Сэм Барнум,
17

Когда вы компилируете, PatternJava выполняет некоторые вычисления, чтобы Stringускорить поиск совпадений в s. (Строит представление регулярного выражения в памяти)

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

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

jjnguy
источник
5
Конечно, вы можете записать все в одну строчку Matcher matched = Pattern.compile(regex).matcher(text);. У этого есть преимущества по сравнению с введением одного метода: аргументы эффективно именуются, и очевидно, как исключить из них Patternдля повышения производительности (или разделить между методами).
Том Хотин - tackline
1
Всегда кажется, что ты так много знаешь о Java. Они должны нанять вас, чтобы вы работали на них ...
jjnguy
5

Это вопрос производительности и использования памяти, скомпилируйте и сохраните соблюдаемый шаблон, если вам нужно его много использовать. Типичное использование регулярного выражения - это проверенный пользовательский ввод (формат) , а также форматирование выходных данных для пользователей , в этих классах сохранение согласованного шаблона кажется вполне логичным, поскольку они обычно вызывали много.

Ниже представлен образец валидатора, которого действительно много называют :)

public class AmountValidator {
    //Accept 123 - 123,456 - 123,345.34
    private static final String AMOUNT_REGEX="\\d{1,3}(,\\d{3})*(\\.\\d{1,4})?|\\.\\d{1,4}";
    //Compile and save the pattern  
    private static final Pattern AMOUNT_PATTERN = Pattern.compile(AMOUNT_REGEX);


    public boolean validate(String amount){

         if (!AMOUNT_PATTERN.matcher(amount).matches()) {
            return false;
         }    
        return true;
    }    
}

Как упоминал @Alan Moore, если в вашем коде есть многоразовое регулярное выражение (например, перед циклом), вы должны скомпилировать и сохранить шаблон для повторного использования.

Алиреза Фаттахи
источник
2

Pattern.compile()позволяет многократно использовать регулярное выражение (это потокобезопасно). Прирост производительности может быть весьма значительным.

Я сделал быстрый тест:

    @Test
    public void recompile() {
        var before = Instant.now();
        for (int i = 0; i < 1_000_000; i++) {
            Pattern.compile("ab").matcher("abcde").matches();
        }
        System.out.println("recompile " + Duration.between(before, Instant.now()));
    }

    @Test
    public void compileOnce() {
        var pattern = Pattern.compile("ab");
        var before = Instant.now();
        for (int i = 0; i < 1_000_000; i++) {
            pattern.matcher("abcde").matches();
        }
        System.out.println("compile once " + Duration.between(before, Instant.now()));
    }

compileOnce был в 3–4 раза быстрее . Я думаю, это сильно зависит от самого регулярного выражения, но для часто используемого регулярного выражения я используюstatic Pattern pattern = Pattern.compile(...)

apflieger
источник
0

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

драконорожденные
источник
0

Подобно 'Pattern.compile', есть 'RECompiler.compile' [из com.sun.org.apache.regexp.internal], где:
1. скомпилированный код для шаблона [az] содержит 'az'
2. скомпилированный код для В шаблоне [0-9] есть '09'
3. в скомпилированном коде для шаблона [abc] есть 'aabbcc'.

Скомпилированный таким образом код - отличный способ обобщить несколько случаев. Таким образом, вместо того, чтобы иметь разные ситуации обработки кода 1,2 и 3. Проблема сводится к сравнению с ascii текущего и следующего элементов в скомпилированном коде, следовательно, пар. Таким образом,
a. все, что имеет ascii между a и z, находится между a и z
b. все, что имеет ascii между "a" и "a" определенно является "a"

Девашиш Приядарши
источник
0

Класс Pattern - это точка входа в движок регулярных выражений. Вы можете использовать его через Pattern.matches () и Pattern.comiple (). # Разница между этими двумя. match () - для быстрой проверки, соответствует ли текст (String) заданному регулярному выражению comiple () - создает ссылку на Pattern. Таким образом, можно использовать несколько раз для сопоставления регулярного выражения с несколькими текстами.

Для справки:

public static void main(String[] args) {
     //single time uses
     String text="The Moon is far away from the Earth";
     String pattern = ".*is.*";
     boolean matches=Pattern.matches(pattern,text);
     System.out.println("Matches::"+matches);

    //multiple time uses
     Pattern p= Pattern.compile("ab");
     Matcher  m=p.matcher("abaaaba");
     while(m.find()) {
         System.out.println(m.start()+ " ");
     }
}
vkstream
источник