Рубрики
Без рубрики

Предварительная компиляция Шаблонов Регулярных Выражений В Объекты Шаблонов

Узнайте о преимуществах шаблона регулярных выражений перед компиляцией и новых методах, представленных в Java 8 и 11.

Автор оригинала: Fabio Silva.

1. Обзор

В этом уроке мы увидим преимущества предварительной компиляции шаблона регулярных выражений и новые методы, представленные в Java 8 и 11 .

Это не будет руководство по регулярному выражению, но у нас есть отличное руководство по API регулярных выражений Java для этой цели.

2. Преимущества

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

Давайте рассмотрим этот принцип, поскольку он относится к Pattern#compile. W e будет использовать простой бенчмарк :

  1. У нас есть список с 5 000 000 номеров от 1 до 5 000 000
  2. Наше регулярное выражение будет соответствовать четным числам

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

  • String.matches(регулярное выражение)
  • Pattern.matches(регулярное выражение, последовательность символов)
  • Pattern.compile(регулярное выражение).matcher(CharSequence).matches()
  • Предварительно скомпилированное регулярное выражение с множеством вызовов preCompiled Pattern.matcher(value).спички()
  • Предварительно скомпилированное регулярное выражение с одним экземпляром Matcher и множеством вызовов совпадений из предварительно скомпилированного шаблона.reset(значение).спички()

На самом деле, если мы посмотрим на реализацию String#matches :

public boolean matches(String regex) {
    return Pattern.matches(regex, this);
}

И в Шаблон#соответствует :

public static boolean matches(String regex, CharSequence input) {
    Pattern p = compile(regex);
    Matcher m = p.matcher(input);
    return m.matches();
}

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

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

    
@Benchmark
public void matcherFromPreCompiledPatternResetMatches(Blackhole bh) {
    for (String value : values) {
        bh.consume(matcherFromPreCompiledPattern.reset(value).matches());
    }
}

@Benchmark
public void preCompiledPatternMatcherMatches(Blackhole bh) {
    for (String value : values) {
        bh.consume(preCompiledPattern.matcher(value).matches());
    }
}

@Benchmark
public void patternCompileMatcherMatches(Blackhole bh) {
    for (String value : values) {
        bh.consume(Pattern.compile(PATTERN).matcher(value).matches());
    }
}

@Benchmark
public void patternMatches(Blackhole bh) {
    for (String value : values) {
        bh.consume(Pattern.matches(PATTERN, value));
    }
}

@Benchmark
public void stringMatchs(Blackhole bh) {
    Instant start = Instant.now();
    for (String value : values) {
        bh.consume(value.matches(PATTERN));
    }
}

Глядя на результаты бенчмарка, нет никаких сомнений в том, что предварительно скомпилированный Шаблон и уменьшенные Совпадения являются победителями с результатом более чем в шесть раз быстрее :

Benchmark                                                               Mode  Cnt     Score     Error  Units
PatternPerformanceComparison.matcherFromPreCompiledPatternResetMatches  avgt   20   278.732 ±  22.960  ms/op
PatternPerformanceComparison.preCompiledPatternMatcherMatches           avgt   20   500.393 ±  34.182  ms/op
PatternPerformanceComparison.stringMatchs                               avgt   20  1433.099 ±  73.687  ms/op
PatternPerformanceComparison.patternCompileMatcherMatches               avgt   20  1774.429 ± 174.955  ms/op
PatternPerformanceComparison.patternMatches                             avgt   20  1792.874 ± 130.213  ms/op

Помимо времени выполнения, у нас также есть количество созданных объектов :

  • Первые три формы:
    • 5 000 000 Шаблон созданные экземпляры
    • 5 000 000 Matcher созданных экземпляров
  • Предварительно скомпилированный шаблон.matcher(значение).спички()
    • 1 Шаблон экземпляр создан
    • 5 000 000 Matcher созданных экземпляров
  • совпадения Из предварительно Скомпилированного Шаблона.сброс(значение).спички()
    • 1 Шаблон экземпляр создан
    • 1 Сопоставитель экземпляр создан

Таким образом, вместо делегирования нашего регулярного выражения String#matches или Pattern#matches , который всегда будет создавать экземпляры Pattern и Matcher . Мы должны предварительно скомпилировать ваше регулярное выражение, чтобы повысить производительность и создать меньше объектов.

Чтобы узнать больше о производительности в регулярных выражениях, ознакомьтесь с нашим обзором производительности регулярных выражений в Java.

3. Новые Методы

С появлением функциональных интерфейсов и потоков повторное использование стало проще.

Класс Pattern эволюционировал в новых версиях Java , чтобы обеспечить интеграцию с потоками и лямбдами.

3.1. Java 8

Java 8 представила два новых метода: splitAsStream и как Предикат .

Давайте рассмотрим некоторый код для splitAsStream , который создает поток из заданной входной последовательности вокруг совпадений шаблона:

@Test
public void givenPreCompiledPattern_whenCallSplitAsStream_thenReturnArraySplitByThePattern() {
    Pattern splitPreCompiledPattern = Pattern.compile("__");
    Stream textSplitAsStream = splitPreCompiledPattern.splitAsStream("My_Name__is__Fabio_Silva");
    String[] textSplit = textSplitAsStream.toArray(String[]::new);

    assertEquals("My_Name", textSplit[0]);
    assertEquals("is", textSplit[1]);
    assertEquals("Fabio_Silva", textSplit[2]);
}

Метод as Predicate создает предикат, который ведет себя так, как если бы он создавал сопоставитель из входной последовательности, а затем вызывал find:

string -> matcher(string).find();

Давайте создадим шаблон, который соответствует именам из списка, содержащего по крайней мере имя и фамилию с по крайней мере тремя буквами в каждом:

@Test
public void givenPreCompiledPattern_whenCallAsPredicate_thenReturnPredicateToFindPatternInTheList() {
    List namesToValidate = Arrays.asList("Fabio Silva", "Mr. Silva");
    Pattern firstLastNamePreCompiledPattern = Pattern.compile("[a-zA-Z]{3,} [a-zA-Z]{3,}");
    
    Predicate patternsAsPredicate = firstLastNamePreCompiledPattern.asPredicate();
    List validNames = namesToValidate.stream()
        .filter(patternsAsPredicate)
        .collect(Collectors.toList());

    assertEquals(1,validNames.size());
    assertTrue(validNames.contains("Fabio Silva"));
}

3.2. Java 11

Java 11 представила предикат as Match метод , который создает предикат, который ведет себя так, как если бы он создавал сопоставитель из входной последовательности, а затем вызывал совпадения:

string -> matcher(string).matches();

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

@Test
public void givenPreCompiledPattern_whenCallAsMatchPredicate_thenReturnMatchPredicateToMatchesPattern() {
    List namesToValidate = Arrays.asList("Fabio Silva", "Fabio Luis Silva");
    Pattern firstLastNamePreCompiledPattern = Pattern.compile("[a-zA-Z]{3,} [a-zA-Z]{3,}");
        
    Predicate patternAsMatchPredicate = firstLastNamePreCompiledPattern.asMatchPredicate();
    List validatedNames = namesToValidate.stream()
        .filter(patternAsMatchPredicate)
        .collect(Collectors.toList());

    assertTrue(validatedNames.contains("Fabio Silva"));
    assertFalse(validatedNames.contains("Fabio Luis Silva"));
}

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

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

Мы также узнали о трех новых методах, введенных в JDK 8 и JDK 11, которые облегчают нашу жизнь .

Код для этих примеров доступен на GitHub в core-java-11 для фрагментов JDK11 и core-java-regex для других.