java.util.regex - znaczenie Pattern.compile ()?


118

Jakie jest znaczenie Pattern.compile()metody?
Dlaczego przed pobraniem Matcherobiektu muszę skompilować ciąg wyrażenia regularnego ?

Na przykład :

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

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

2
Cóż, znaczenie jest prawie BRAK, jeśli implementacja (jak w JDK 1.7) jest zwykłym SKRÓTEM do nowego wzorca (regex, 0); To powiedziawszy, PRAWDZIWE znaczenie nie polega na samej metodzie statycznej, ale na utworzeniu i zwróceniu nowego wzorca, który można zapisać do późniejszego wykorzystania. Być może istnieją inne implementacje, w których metoda statyczna przyjmuje nową trasę i buforuje obiekty Pattern, a to byłby prawdziwy przypadek znaczenia Pattern.compile ()!
marcolopes,

Odpowiedzi podkreślają znaczenie oddzielania wzorca i dopasowywania klas (o co prawdopodobnie chodzi w pytaniu), ale nikt nie odpowiada, dlaczego nie możemy po prostu użyć konstruktora new Pattern(regex)zamiast statycznej funkcji kompilującej. komentarz marcolopes jest na miejscu.
kon psych

Odpowiedzi:


144

compile()Metoda nazywana jest zawsze w pewnym momencie; to jedyny sposób na stworzenie obiektu Pattern. Tak więc pytanie brzmi naprawdę, dlaczego miałbyś to nazwać wyraźnie ? Jednym z powodów jest to, że potrzebujesz odniesienia do obiektu Matcher, aby móc używać jego metod, takich jak group(int)pobieranie zawartości grup przechwytywania. Jedynym sposobem na zdobycie obiektu Matcher jest użycie matcher()metody obiektu Pattern , a jedynym sposobem na uzyskanie uchwytu obiektu Pattern jest użycie compile()metody. Następnie jest find()metoda, która w przeciwieństwie do matches()klas String lub Pattern nie jest duplikowana.

Innym powodem jest unikanie ciągłego tworzenia tego samego obiektu Pattern. Za każdym razem, gdy używasz jednej z metod opartych na wyrażeniach regularnych w łańcuchu (lub matches()metody statycznej we wzorcu), tworzy ona nowy wzorzec i nowy element dopasowujący. Więc ten fragment kodu:

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

... jest dokładnie równoważne z tym:

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

Oczywiście robi to dużo niepotrzebnej pracy. W rzeczywistości skompilowanie wyrażenia regularnego i utworzenie wystąpienia obiektu Pattern może z łatwością zająć więcej czasu niż wykonanie rzeczywistego dopasowania. Dlatego zwykle ma sens wyciągnięcie tego kroku z pętli. Możesz również stworzyć Matchera z wyprzedzeniem, chociaż nie są one tak drogie:

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

Jeśli znasz wyrażenia regularne .NET, możesz się zastanawiać, czy compile()metoda Javy jest powiązana z RegexOptions.Compiledmodyfikatorem .NET ; odpowiedź brzmi nie. Pattern.compile()Metoda Java jest po prostu odpowiednikiem konstruktora Regex platformy .NET. Po określeniu Compiledopcji:

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

... kompiluje wyrażenie regularne bezpośrednio do kodu bajtowego CIL, dzięki czemu może działać znacznie szybciej, ale przy znacznych kosztach przetwarzania wstępnego i wykorzystania pamięci - potraktuj to jako sterydy dla wyrażeń regularnych. Java nie ma odpowiednika; nie ma różnicy między wzorcem, który jest tworzony za kulisami, String#matches(String)a tym, który tworzysz jawnie Pattern#compile(String).

(EDYCJA: Pierwotnie powiedziałem, że wszystkie obiekty .NET Regex są buforowane, co jest niepoprawne. Od czasu .NET 2.0 automatyczne buforowanie występuje tylko w przypadku metod statycznych, takich jak Regex.Matches()bezpośrednie wywołanie konstruktora Regex. Ref )


1
Jednak to nie wyjaśnia znaczenia takiej metody TRIVIAL w klasie Pattern! Zawsze zakładałem, że statyczna metoda Pattern.compile była czymś więcej niż prostym skrótem do nowego wzorca (regex, 0); Spodziewałem się CACHE skompilowanych wzorców ... myliłem się. Może tworzenie pamięci podręcznej jest droższe niż tworzenie nowych wzorów ??!
marcolopes,

9
Należy pamiętać, że klasa Matcher nie jest bezpieczna dla wątków i nie powinna być udostępniana między wątkami. Z drugiej strony Pattern.compile () to.
gswierczynski

1
TLDR; „... [Pattern.compile (...)] kompiluje wyrażenie regularne bezpośrednio do kodu bajtowego CIL, dzięki czemu działa znacznie szybciej, ale przy znacznych kosztach przetwarzania z góry i wykorzystania pamięci”
sean.boyer

3
Chociaż prawdą jest, że dopasowywanie nie jest tak drogie jak Pattern.compile, przeprowadziłem kilka pomiarów w scenariuszu, w którym dochodziło do tysięcy dopasowań wyrażeń regularnych i była dodatkowa, bardzo znacząca oszczędność dzięki utworzeniu Matchera z wyprzedzeniem i ponownym użyciu go za pośrednictwem dopasowywania .Resetowanie(). Unikanie tworzenia nowych obiektów w stercie w metodach wywoływanych tysiące razy jest zwykle znacznie mniej obciążające procesor, pamięć, a tym samym GC.
Volksman

@Volksman to nie jest bezpieczna rada ogólna, ponieważ obiekty Matcher nie są bezpieczne dla wątków. Nie dotyczy to również pytania. Ale tak, możesz resetobiekt Matcher, który jest kiedykolwiek używany tylko przez jeden wątek na raz, aby zmniejszyć przydziały.
AndrewF,

40

Kompiluj analizuje wyrażenie regularne i tworzy reprezentację w pamięci . Koszt kompilacji jest znaczący w porównaniu z dopasowaniem. Jeśli używasz wzorca wielokrotnie , buforowanie skompilowanego wzorca będzie miało pewną wydajność.


7
Dodatkowo możesz określić flagi takie jak case_insensitive, dot_all itp. Podczas kompilacji, przekazując dodatkowy parametr flagi
Sam Barnum.

17

Podczas kompilacji PatternJava wykonuje pewne obliczenia, aby przyspieszyć wyszukiwanie dopasowań w Strings. (Buduje w pamięci reprezentację wyrażenia regularnego)

Jeśli zamierzasz używać Patternwielokrotnie, zobaczysz ogromny wzrost wydajności w porównaniu z tworzeniem za Patternkażdym razem nowego .

W przypadku użycia wzorca tylko raz, krok kompilacji wydaje się po prostu dodatkowym wierszem kodu, ale w rzeczywistości może być bardzo pomocny w ogólnym przypadku.


5
Oczywiście możesz to wszystko zapisać w jednej linii Matcher matched = Pattern.compile(regex).matcher(text);. Ma to swoje zalety w porównaniu z wprowadzeniem pojedynczej metody: argumenty są skutecznie nazywane i jest oczywiste, jak Patternrozłożyć na czynniki, aby uzyskać lepszą wydajność (lub podzielić na metody).
Tom Hawtin - tackline

1
Zawsze wydaje się, że wiesz tak dużo o Javie. Powinni zatrudnić cię do pracy ...
jjnguy

5

Jest to kwestia wydajności i wykorzystania pamięci, skompiluj i zachowaj zgodny wzorzec, jeśli chcesz go często używać. Typowym zastosowaniem wyrażenia regularnego jest walidacja danych wejściowych użytkownika (format) , a także formatowanie danych wyjściowych dla użytkowników , w tych klasach, zapisywanie zgodnego wzorca, wydaje się całkiem logiczne, ponieważ zwykle nazywali dużo.

Poniżej znajduje się przykładowy walidator, który naprawdę nazywa się dużo :)

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;
    }    
}

Jak wspomniał @Alan Moore, jeśli masz w kodzie wyrażenie regularne wielokrotnego użytku (na przykład przed pętlą), musisz skompilować i zapisać wzorzec do ponownego wykorzystania.


2

Pattern.compile()pozwalają na wielokrotne użycie wyrażenia regularnego (jest bezpieczne dla wątków). Korzyść z wydajności może być dość znacząca.

Zrobiłem szybki test porównawczy:

    @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 był od 3x do 4x szybszy . Myślę, że w dużym stopniu zależy to od samego wyrażenia regularnego, ale w przypadku często używanego wyrażenia regularnego wybieram rozszerzeniestatic Pattern pattern = Pattern.compile(...)


0

Wstępne kompilowanie wyrażenia regularnego zwiększa szybkość. Ponowne użycie Matchera daje kolejne niewielkie przyspieszenie. Jeśli metoda jest wywoływana często, powiedzmy, że jest wywoływana w pętli, ogólna wydajność z pewnością wzrośnie.


0

Podobnie jak „Pattern.compile” istnieje „RECompiler.compile” [z com.sun.org.apache.regexp.internal], gdzie:
1. skompilowany kod wzorca [az] zawiera „az”
2. skompilowany kod dla wzorzec [0-9] zawiera '09'
3. skompilowany kod wzorca [abc] zawiera 'aabbcc'.

Zatem skompilowany kod jest świetnym sposobem na uogólnienie wielu przypadków. Dlatego zamiast mieć różne sytuacje obsługi kodu 1, 2 i 3. Problem sprowadza się do porównania z ascii obecnego i następnego elementu w skompilowanym kodzie, stąd pary. Zatem
a. wszystko z ascii między a i z jest między a i z
b. wszystko z ascii między „a” jest zdecydowanie „a”


0

Klasa Pattern jest punktem wejścia silnika regex i można jej używać poprzez Pattern.matches () i Pattern.comiple (). # Różnica między tymi dwoma. match () - aby szybko sprawdzić, czy tekst (String) pasuje do podanego wyrażenia regularnego comiple () - utwórz odniesienie do Pattern. Więc może użyć wiele razy, aby dopasować wyrażenie regularne do wielu tekstów.

Na przykład:

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()+ " ");
     }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.