java.util.regex - Bedeutung von Pattern.compile ()?

118

Welche Bedeutung hat die Pattern.compile()Methode?
Warum muss ich die Regex-Zeichenfolge kompilieren, bevor ich das MatcherObjekt erhalte ?

Zum Beispiel :

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

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);
Sidharth
quelle
2
Nun, die Wichtigkeit ist fast KEINE, wenn die Implementierung (wie in JDK 1.7) nur ein KURZSCHLUSS zu einem neuen Muster ist (Regex, 0); Die WIRKLICHE Bedeutung ist jedoch nicht die statische Methode selbst, sondern die Erstellung und Rückgabe eines neuen Musters, das für die spätere Verwendung gespeichert werden kann. Vielleicht gibt es andere Implementierungen, bei denen die statische Methode einen neuen Weg einschlägt und die Pattern-Objekte zwischenspeichert, und das wäre ein echter Fall von Pattern.compile ()!
marcolopes
Die Antworten unterstreichen die Wichtigkeit der Trennung von Muster- und Übereinstimmungsklassen (was wahrscheinlich die Frage ist), aber niemand antwortet, warum wir nicht einfach einen Konstruktor new Pattern(regex)anstelle einer statischen Kompilierungsfunktion verwenden können. marcolopes Kommentar ist vor Ort.
Kon Psych

Antworten:

143

Die compile()Methode wird immer irgendwann aufgerufen; Dies ist die einzige Möglichkeit, ein Musterobjekt zu erstellen. Die Frage ist also wirklich, warum Sie es explizit nennen sollten . Ein Grund dafür ist, dass Sie einen Verweis auf das Matcher-Objekt benötigen, damit Sie dessen Methoden verwenden können, group(int)um beispielsweise den Inhalt von Erfassungsgruppen abzurufen. Die einzige Möglichkeit, das Matcher-Objekt in den Griff zu bekommen, ist die matcher()Methode des Pattern-Objekts , und die einzige Möglichkeit, das Pattern-Objekt in den Griff zu bekommen, ist die compile()Methode. Dann gibt es die find()Methode, die im Gegensatz zu matches()den Klassen String oder Pattern nicht dupliziert wird.

Der andere Grund besteht darin, zu vermeiden, dass immer wieder dasselbe Musterobjekt erstellt wird. Jedes Mal, wenn Sie eine der regex-basierten Methoden in String (oder die statische matches()Methode in Pattern) verwenden, werden ein neues Pattern und ein neuer Matcher erstellt. Also dieses Code-Snippet:

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

... ist genau gleichbedeutend damit:

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

Das macht natürlich viel unnötige Arbeit. Tatsächlich kann das Kompilieren des regulären Ausdrucks und das Instanziieren des Musterobjekts leicht länger dauern als das Durchführen einer tatsächlichen Übereinstimmung. Daher ist es normalerweise sinnvoll, diesen Schritt aus der Schleife zu ziehen. Sie können den Matcher auch im Voraus erstellen, obwohl er bei weitem nicht so teuer ist:

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

Wenn Sie mit .NET-Regexen vertraut sind, fragen Sie sich möglicherweise, ob die Java- compile()Methode mit dem .NET- RegexOptions.CompiledModifikator zusammenhängt. Die Antwort ist nein. Die Java- Pattern.compile()Methode entspricht lediglich dem Regex-Konstruktor von .NET. Wenn Sie die CompiledOption angeben :

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

... kompiliert den regulären Ausdruck direkt in CIL-Bytecode, wodurch er eine viel schnellere Leistung erbringt, jedoch zu erheblichen Kosten bei der Vorabverarbeitung und der Speichernutzung - stellen Sie sich ihn als Steroide für reguläre Ausdrücke vor. Java hat kein Äquivalent; Es gibt keinen Unterschied zwischen einem Muster, das hinter den Kulissen erstellt wurde, String#matches(String)und einem Muster, mit dem Sie explizit erstellen Pattern#compile(String).

(BEARBEITEN: Ich habe ursprünglich gesagt, dass alle .NET Regex-Objekte zwischengespeichert werden, was falsch ist. Seit .NET 2.0 erfolgt das automatische Caching nur mit statischen Methoden wie Regex.Matches(), nicht wenn Sie einen Regex-Konstruktor direkt aufrufen. Ref )

Alan Moore
quelle
1
Dies erklärt jedoch nicht die Bedeutung einer solchen TRIVIAL-Methode für die Pattern-Klasse! Ich habe immer angenommen, dass die statische Methode Pattern.compile viel mehr als ein einfacher SHORTCUT für ein neues Pattern (Regex, 0) ist. Ich hatte einen CACHE kompilierter Muster erwartet ... ich habe mich geirrt. Vielleicht ist das Erstellen eines Caches teurer als das Erstellen neuer Muster ??!
marcolopes
9
Bitte beachten Sie, dass die Matcher-Klasse nicht threadsicher ist und nicht für mehrere Threads freigegeben werden sollte. Auf der anderen Seite ist Pattern.compile ().
gswierczynski
1
TLDR; "... [Pattern.compile (...)] kompiliert den regulären Ausdruck direkt in CIL-Bytecode, wodurch er viel schneller ausgeführt werden kann, jedoch zu erheblichen Kosten bei der
Vorabverarbeitung
3
Zwar sind Matcher nicht annähernd so teuer wie Pattern.compile. Ich habe einige Metriken in einem Szenario durchgeführt, in dem Tausende von Regex-Übereinstimmungen stattfanden, und es gab eine zusätzliche, sehr bedeutende Einsparung, indem der Matcher im Voraus erstellt und über Matcher wiederverwendet wurde .reset (). Das Vermeiden der Erstellung neuer Objekte im Heap in tausendfach aufgerufenen Methoden schont normalerweise CPU, Speicher und damit den GC erheblich.
Volksman
@Volksman, das ist kein sicherer allgemeiner Rat, da Matcher-Objekte nicht threadsicher sind. Es ist auch nicht relevant für die Frage. Aber ja, Sie könnten resetein Matcher-Objekt verwenden, das immer nur von einem Thread gleichzeitig verwendet wird, um die Zuordnungen zu reduzieren.
AndrewF
40

Compile analysiert den regulären Ausdruck und erstellt eine speicherinterne Darstellung . Der zu kompilierende Aufwand ist im Vergleich zu einer Übereinstimmung erheblich. Wenn Sie ein Muster wiederholt verwenden , wird die Leistung zum Zwischenspeichern des kompilierten Musters verbessert.

Thomas Jung
quelle
7
Außerdem können Sie während der Kompilierung Flags wie case_insensitive, dot_all usw. angeben, indem Sie einen zusätzlichen Flags-Parameter übergeben
Sam Barnum,
17

Beim Kompilieren führt PatternJava einige Berechnungen durch, um das Finden von Übereinstimmungen in Strings zu beschleunigen. (Erstellt eine speicherinterne Darstellung des regulären Ausdrucks)

Wenn Sie die PatternMehrfachverwendung wiederverwenden, wird die Leistung erheblich gesteigert, wenn Sie Patternjedes Mal eine neue erstellen .

Wenn Sie das Muster nur einmal verwenden, scheint der Kompilierungsschritt nur eine zusätzliche Codezeile zu sein, kann jedoch im allgemeinen Fall sehr hilfreich sein.

jjnguy
quelle
5
Natürlich können Sie alles in einer Zeile schreiben Matcher matched = Pattern.compile(regex).matcher(text);. Dies hat gegenüber der Einführung einer einzelnen Methode Vorteile: Die Argumente werden effektiv benannt, und es ist offensichtlich, wie die Patternfür eine bessere Leistung herausgerechnet werden kann (oder auf mehrere Methoden aufgeteilt werden kann).
Tom Hawtin - Tackline
1
Es scheint immer so, als wüssten Sie so viel über Java. Sie sollten Sie einstellen, um für sie zu arbeiten ...
jjnguy
5

Es ist eine Frage der Leistung und der Speichernutzung. Kompilieren und behalten Sie das konforme Muster, wenn Sie es häufig verwenden müssen. Eine typische Verwendung von Regex besteht darin, Benutzereingaben (Format) zu validieren und Ausgabedaten für Benutzer in diesen Klassen zu formatieren. Das Speichern des übereinstimmenden Musters erscheint ziemlich logisch, da sie normalerweise häufig aufgerufen werden.

Unten ist ein Beispielvalidator, der wirklich viel genannt wird :)

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

Wie von @Alan Moore erwähnt, müssen Sie Muster für die Wiederverwendung kompilieren und speichern, wenn Sie wiederverwendbaren regulären Ausdruck in Ihrem Code haben (z. B. vor einer Schleife).

Alireza Fattahi
quelle
2

Pattern.compile()Erlauben Sie die mehrfache Wiederverwendung eines regulären Ausdrucks (es ist threadsicher). Der Leistungsvorteil kann erheblich sein.

Ich habe einen schnellen Benchmark durchgeführt:

    @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 war zwischen 3x und 4x schneller . Ich denke, es hängt stark von der Regex selbst ab, aber für eine Regex, die oft verwendet wird, entscheide ich mich für einestatic Pattern pattern = Pattern.compile(...)

apflieger
quelle
0

Das Vorkompilieren des regulären Ausdrucks erhöht die Geschwindigkeit. Die Wiederverwendung des Matchers führt zu einer weiteren leichten Beschleunigung. Wenn die Methode häufig aufgerufen wird und beispielsweise innerhalb einer Schleife aufgerufen wird, steigt die Gesamtleistung mit Sicherheit.

DragonBorn
quelle
0

Ähnlich wie bei 'Pattern.compile' gibt es 'RECompiler.compile' [von com.sun.org.apache.regexp.internal], wobei:
1. kompilierter Code für Muster [az] 'az' enthält
2. kompilierter Code für Muster [0-9] enthält '09'.
3. Kompilierter Code für Muster [abc] enthält 'aabbcc'.

Kompilierter Code ist daher eine großartige Möglichkeit, mehrere Fälle zu verallgemeinern. Anstatt also unterschiedliche Codebehandlungssituationen 1,2 und 3 zu haben. Das Problem reduziert sich auf den Vergleich mit den ASCII des gegenwärtigen und nächsten Elements im kompilierten Code, daher die Paare. Also
a. alles mit ascii zwischen a und z liegt zwischen a und z
b. alles mit ascii zwischen 'a und a' ist definitiv 'a'

Devashish Priyadarshi
quelle
0

Die Musterklasse ist der Einstiegspunkt der Regex-Engine. Sie können sie über Pattern.matches () und Pattern.comiple () verwenden. #Differenz zwischen diesen beiden. matches () - für schnell überprüfen , ob ein Text (String) einen bestimmten regulären Ausdruck comiple () - erstellen die Referenz - Muster. Kann also mehrmals verwendet werden, um den regulären Ausdruck mit mehreren Texten abzugleichen.

Als Referenz:

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
quelle