Ein Überblick über die Leistung regulärer Ausdrücke in Java

1. Übersicht

In diesem kurzen Tutorial zeigen wir, wie die Pattern-Matching-Engine funktioniert. Wir werden auch verschiedene Möglichkeiten zur Optimierung regulärer Ausdrücke in Java vorstellen .

Eine Einführung in die Verwendung regulärer Ausdrücke finden Sie in diesem Artikel hier.

2. Die Pattern-Matching-Engine

Das Paket java.util.regex verwendet eine Art Mustervergleichs-Engine, die als nichtdeterministischer endlicher Automat (NFA) bezeichnet wird. Es wird als nicht deterministisch angesehen, da beim Versuch, einen regulären Ausdruck für eine bestimmte Zeichenfolge abzugleichen, jedes Zeichen in der Eingabe möglicherweise mehrmals mit verschiedenen Teilen des regulären Ausdrucks verglichen wird.

Im Hintergrund verwendet die oben erwähnte Engine Backtracking . Dieser allgemeine Algorithmus versucht, alle Möglichkeiten auszuschöpfen, bis er einen Fehler deklariert. Betrachten Sie das folgende Beispiel, um die NFA besser zu verstehen :

"tra(vel|ce|de)m"

Mit der Eingabe StringReise „, zuerst der Motor sucht ‚ tra ‘ und es sofort finden.

Danach wird versucht, ab dem vierten Zeichen mit „ vel “ übereinzustimmen. Dies wird übereinstimmen, also wird es vorwärts gehen und versuchen, mit „ m “ übereinzustimmen.

Das passt nicht zusammen, und aus diesem Grund geht es zurück zum vierten Zeichen und sucht nach „ ce “. Auch dies wird nicht übereinstimmen, also wird es wieder auf die vierte Position zurückkehren und es mit „ de “ versuchen . Diese Zeichenfolge stimmt auch nicht überein und kehrt daher zum zweiten Zeichen in der Eingabezeichenfolge zurück und versucht, nach einem anderen „ tra “ zu suchen .

Mit dem letzten Fehler gibt der Algorithmus einen Fehler zurück.

Mit dem einfachen letzten Beispiel hatte der Motor mehrmals einen Rückzieher beim Versuch , den Eingang entsprechen String des regulären Ausdruck. Aus diesem Grund ist es wichtig, den Umfang des Backtrackings zu minimieren.

3. Möglichkeiten zur Optimierung regulärer Ausdrücke

3.1. Vermeiden Sie eine Neukompilierung

Reguläre Ausdrücke in Java werden zu einer internen Datenstruktur kompiliert. Diese Zusammenstellung ist zeitaufwändig.

Jedes Mal, wenn wir die Methode String.matches (String regex) aufrufen , wird der angegebene reguläre Ausdruck neu kompiliert:

if (input.matches(regexPattern)) { // do something }

Wie wir sehen können, wird jedes Mal, wenn die Bedingung ausgewertet wird, der Regex-Ausdruck kompiliert.

Zur Optimierung ist es möglich, zuerst das Muster zu kompilieren und dann einen Matcher zu erstellen , um die Übereinstimmungen im Wert zu finden:

Pattern pattern = Pattern.compile(regexPattern); for(String value : values) { Matcher matcher = pattern.matcher(value); if (matcher.matches()) { // do something } }

Eine Alternative zur obigen Optimierung ist die Verwendung derselben Matcher- Instanz mit ihrer reset () -Methode:

Pattern pattern = Pattern.compile(regexPattern); Matcher matcher = pattern.matcher(""); for(String value : values) { matcher.reset(value); if (matcher.matches()) { // do something } }

Da Matcher nicht threadsicher ist, müssen wir bei der Verwendung dieser Variante vorsichtig sein. In Multithread-Szenarien kann dies wahrscheinlich gefährlich sein.

Zusammenfassend lässt sich sagen, dass es in jeder Situation, in der wir sicher sind, dass zu einem bestimmten Zeitpunkt nur ein Benutzer des Matchers vorhanden ist, in Ordnung ist, ihn beim Zurücksetzen wiederzuverwenden . Im Übrigen reicht es aus, das vorkompilierte wiederzuverwenden.

3.2. Arbeiten mit Alternation

Wie wir gerade im letzten Abschnitt überprüft haben, kann die unzureichende Verwendung von Alternativen die Leistung beeinträchtigen. Es ist wichtig, Optionen zu platzieren, die eher im Vordergrund stehen, damit sie schneller abgeglichen werden können.

Außerdem müssen wir gemeinsame Muster zwischen ihnen extrahieren. Es ist nicht dasselbe zu sagen:

(travel | trade | trace)

Als:

tra(vel | de | ce)

Letzteres ist schneller, da die NFA versucht, " tra " zu finden, und keine der Alternativen ausprobiert, wenn sie es nicht findet.

3.3. Gruppen erfassen

Jedes Mal, wenn wir Gruppen erobern, wird eine kleine Zeitstrafe verhängt.

Wenn wir den Text nicht innerhalb einer Gruppe erfassen müssen, sollten wir die Verwendung von nicht erfassbaren Gruppen in Betracht ziehen. Verwenden Sie anstelle von „ (M) “ bitte „ (?: M) “.

4. Fazit

In diesem kurzen Artikel haben wir kurz auf die Funktionsweise von NFA eingegangen . Anschließend haben wir untersucht, wie Sie die Leistung unserer regulären Ausdrücke optimieren können, indem Sie unsere Muster vorkompilieren und einen Matcher wiederverwenden .

Schließlich haben wir auf einige Überlegungen hingewiesen, die wir bei der Arbeit mit Abwechslungen und Gruppen berücksichtigen sollten.

Wie immer finden Sie den vollständigen Quellcode auf GitHub.