java.util.regex - важливість Pattern.compile ()?


118

Яке значення має Pattern.compile()метод?
Чому мені потрібно скомпілювати рядок regex, перш ніж отримати Matcherоб'єкт?

Наприклад :

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

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);

2
Ну, важливість майже НІКОЛЬНА, якщо реалізація (як у JDK 1.7) є лише простим SHORTCUT до нового шаблону (regex, 0); При цьому важливе значення має не сам статичний метод, а створення та повернення нового Шаблону, який можна зберегти для останнього використання. Можливо, є й інші реалізації, де статичний метод приймає новий маршрут і кешує об'єкти Шаблон, і це був би справжній випадок важливості Pattern.compile ()!
марколопи

У відповідях підкреслюється важливість розділення шаблону і узгодження класів (що, мабуть, запитання задається), але ніхто не відповідає, чому ми не можемо просто використовувати конструктор new Pattern(regex)замість статичної функції компіляції. коментар marcolopes є на місці.
kon psych

Відповіді:


144

compile()Метод завжди викликається в якій - то момент; це єдиний спосіб створити об'єкт Pattern. Тож питання справді, чому ви повинні це чітко називати ? Однією з причин є те, що вам потрібна посилання на об'єкт Matcher, щоб ви могли використовувати його методи, як, наприклад, group(int)для отримання вмісту груп захоплення. Єдиний спосіб домогтися об'єкта Matcher - це за допомогою matcher()методу об'єкта Pattern , і єдиний спосіб отримати власність об'єкта Pattern - через compile()метод. Тоді є find()метод, який, на відміну від цього matches(), не дублюється в класах String або 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();
    }
}

Очевидно, що це робить багато непотрібної роботи. Насправді, для складання регексу та інстанції шаблону об'єкта може знадобитися більше часу, ніж це для виконання фактичного відповідності. Тому зазвичай має сенс витягнути цей крок з петлі. Ви також можете створити 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).

(EDIT: Спочатку я говорив, що всі об’єкти .NET Regex є кешованими, що невірно. Оскільки. NET 2.0, автоматичне кешування відбувається лише статичними методами, наприклад Regex.Matches(), не при прямому виклику конструктора Regex. Ref )


1
Однак це не пояснює важливості такого методу ТРІВІАЛЬНОГО класу Шаблон! Я завжди вважав, що статичний метод Pattern.compile був набагато більше, ніж простий SHORTCUT до нового шаблону (regex, 0); Я очікував CACHE складених шаблонів ... я помилявся. Можливо, створення кешу дорожче, ніж створення нових шаблонів ??!
marcolopes

9
Зверніть увагу, що клас Matcher не є безпечним для потоків, і його не слід ділити між потоками. З іншого боку Pattern.compile () є.
gswierczynski

1
TLDR; "... [Pattern.compile (...)] збирає регулярний вираз безпосередньо в байт-код CIL, що дозволяє йому виконуватись набагато швидше, але з значною вартістю при попередній обробці та використанні пам'яті"
sean.boyer

3
Хоча це правда, що Matchers не так вже й дорого, як Pattern.compile, я робив деякі показники за сценарієм, коли траплялися тисячі матчів регулярного виразів, і була додаткова, дуже значна економія, створюючи Matcher достроково і повторно використовуючи його через matcher .reset (). Уникнення створення нових об’єктів у купі методами, які називаються тисячами разів, зазвичай набагато легші для процесора, пам'яті і, таким чином, GC.
Фолксман

@Volksman, що не є безпечною загальною порадою, оскільки об'єкти Matcher не є безпечними для потоків. Це також не стосується питання. Але так, ви могли resetб об'єктом Matcher, який використовується коли-небудь одним потоком одночасно, щоб зменшити виділення.
AndrewF

40

Компіляція аналізує регулярний вираз і створює уявлення в пам'яті . Накладні витрати, які потрібно скласти, є істотними порівняно з матчем. Якщо ви неодноразово використовуєте шаблон, він отримає певну ефективність для кешування складеного шаблону.


7
Крім того, ви можете вказати прапорці типу case_insensitive, dot_all тощо під час компіляції, передавши додатковий параметр прапорців
Sam Barnum

17

При компіляції PatternJava виконує деякі обчислення, щоб Stringшвидше знаходити збіги в s. (Створює регекс в пам'яті)

Якщо ви збираєтеся повторно використовувати Patternкілька разів, ви побачите значне збільшення продуктивності над створенням Patternкожного разу нового .

У разі використання Шаблону лише один раз, крок компіляції просто здається зайвим рядком коду, але насправді він може бути дуже корисним у загальному випадку.


5
Звичайно, ви можете написати це в один рядок Matcher matched = Pattern.compile(regex).matcher(text);. У цьому є переваги введення єдиного методу: аргументи ефективно названі, і очевидно, як визначити ефективність Patternдля кращої ефективності (або розділити між методами).
Том Хотін - тайклін

1
Завжди здається, що ти так багато знаєш про Яву. Вони повинні найняти вас, щоб ви працювали на них ...
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(...)


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.matches () та Pattern.comiple (). # Відмінність між цими двома. match () - для швидкої перевірки, чи збігається текст (String) із заданим регулярним виразом comiple () - створити посилання Шаблону . Таким чином, можна використовувати кілька разів, щоб співставити регулярний вираз проти кількох текстів.

Довідково:

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()+ " ");
     }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.