Getty Images/iStockphoto
Vorteile kompilierter regulärer Ausdrücke in Python und Java
Egal, ob Sie in Java oder Python programmieren, kompilierte reguläre Ausdrücke (Regular Expression, Regex) können Entwicklungsroutinen erheblich beschleunigen.
Einige Entwickler stehen immer wieder vor der Frage, welches der optimale Weg ist, einen bestimmten Parameter aus einem URL-String zu entfernen.
Eine Option ist die Verwendung von String Primitives zum Aufteilen und Verbinden des Parameters – eine Methode, die klar und einfach zu lesen ist. Man kann das gleiche Ergebnis auch mit einer oder zwei Zeilen regulärer Ausdrücke (Regular Expression, Regex) erreichen. Allerdings sind diese notorisch langsam.
Dennoch kann man fragen: wie viel langsamer sind reguläre Ausdrücke im Vergleich zu String Primitives? Und verbessert die Verwendung vorkompilierter regulärer Ausdrücke die Leistung?
Parameter mit String-Manipulation entfernen
Die einfachste und wohl auch lesbarste Methode zum Entfernen eines Parameters besteht darin, den String mit einem Split-Primitiv aufzuteilen, den angegebenen Parameter zu entfernen und den String wieder zusammenzufügen.
Das ist allerdings aufwendig und beansprucht viel Speicherplatz, da Strings in den meisten Sprachen unveränderlich sind.
In Java beläuft sich dies auf vier Zeilen Code:
String[] parts = input.split("&");
List<String> partsList = new ArrayList<>(Arrays.asList(parts));
partsList.removeIf(part -> part.contains("option"));
outputsSplit = String.join("&",partsList);
In Python ist der Code kürzer und prägnanter:
parts = input.split("&")
parts = [part for part in parts if "option" not in part]
output = '&'.join(parts)
Beide tun das Gleiche: eine geteilte Liste verändern und wieder zusammenfügen.
Parameter mit regulären Ausdrücken entfernen
Die andere Möglichkeit, Parameter in Java oder Python zu entfernen, besteht darin, einen regulären Ausdruck zu verwenden, um den übereinstimmenden Teil des Input Strings zu löschen. Diese Methode ist weit weniger lesbar, benötigt aber weniger Codezeilen. Dies ist ein Anwendungsfall, für den sich reguläre Ausdrücke gut eignen.
In Java ist dafür eine einzige Zeile Code erforderlich:
output = input.replaceFirst("option=one$","").replaceFirst("option=one&","");
In Python sind es zwei Zeilen Code:
output = re.sub(r "option=one$", "", input)
output = re.sub(r "option=one&", "", output)
Der Pythonic-Weg benötigt weniger Codezeilen als String Primitives. Es ist ein gut geeigneter Anwendungsfall für reguläre Ausdrücke, obwohl der Haken ist, dass reguläre Ausdrücke notorisch langsam sind.
Was können Sie tun, wenn Sie das im Voraus wissen? Es gibt eine andere Möglichkeit.
Kompilierte reguläre Ausdrücke zum Entfernen von Parametern
Wenn sich der reguläre Ausdruck, den Sie für den zu löschenden Abschnitt verwenden, nicht ändert, können Sie stattdessen so genannte kompilierte reguläre Ausdrücke verwenden.
Betrachten Sie reguläre Ausdrücke als ein Rezept dafür, wonach in einem String gesucht werden soll. Der Computer muss das Rezept jedes Mal lesen und analysieren, wenn es verwendet wird. Dies ist im Allgemeinen der leistungsintensivste Teil der Verwendung regulärer Ausdrücke.
Kompilierte reguläre Ausdrücke tun dies nur einmal, was sie viel schneller macht als normale reguläre Ausdrücke, obwohl sie nicht verwendet werden können, wenn sich das Muster ändert.
Der Nachteil ist, dass die Anzahl der Codezeilen gegenüber der ursprünglichen Version mit String Primitives steigt, was die ursprüngliche Motivation für die Verwendung regulärer Ausdrücke war.
In Java kommen wir auf sechs Zeilen:
private static final Pattern PATTERN_END = Pattern.compile("&option=one$");
private static final Pattern PATTERN_START_MIDDLE = Pattern.compile("option=one&");
Matcher matcherEnd = PATTERN_END.matcher(input);
String intermediateResult = matcherEnd.replaceFirst("");
Matcher matcherStartMiddle = PATTERN_START_MIDDLE.matcher(intermediateResult);
output = matcherStartMiddle.replaceFirst("");
In Python benötigen wir dafür vier Zeilen:
PATTERN_END = re.compile(r"&option=one$")
PATTERN_START_MIDDLE = re.compile(r"option=one&")
intermediate_result = PATTERN_END.sub('', input)
output = PATTERN_START_MIDDLE.sub('', intermediate_result)
Die eigentliche Frage ist also: Ist die Zunahme an Codezeilen den Leistungsgewinn wert?
Um die Methoden gegeneinander zu testen, habe ich einen Code geschrieben, um 100.000 Eingabezeilen zu generieren, und darin den Parameter, den wir entfernen wollen, zufällig platziert. Dann habe ich den Code durch die Funktionen laufen lassen und die Zeit bis zur Fertigstellung gemessen.
Java-Ergebnisse mit kompilierten regulären Ausdrücken
In Java war ich überrascht, wie nahe die Leistung von standardmäßigen regulären Ausdrücken an der von String Primitives lag. String Primitives brauchten 188 ms, um die 100.000 Zeilen zu verarbeiten. Standardmäßige reguläre Ausdrücke brauchten 224 ms für die gleichen 100.000 Zeilen.
Die kompilierten regulären Ausdrücke hingegen waren atemberaubend. Sie schafften es, die 100.000 Zeilen in nur 52 ms zu verarbeiten. Das ist mehr als dreimal so schnell wie String Primitives.
Python-Ergebnisse mit kompilierten regulären Ausdrücken
Die Anwendung der gleichen Methodik mit 100.000 zufällig generierten Eingabezeilen in Python zeigte einige Unterschiede zu Java. Der erste war, dass der Code im Allgemeinen fast fünfmal langsamer war. Das ist bei einer interpretierten Sprache im Vergleich zu einer kompilierten Sprache zu erwarten.
Zweitens gab es einen viel größeren Unterschied zwischen der Verwendung der String Primitives und regulären Ausdrücken. Die Verwendung eines regulären Ausdrucks war fast doppelt so langsam wie die Verwendung der String Primitives. String Primitives verarbeiteten die 100.000 Zeilen in 477 ms, während der reguläre Standardausdruck 904 ms benötigte.
Kompilierte reguläre Ausdrücke schnitten in Python jedoch immer noch vergleichsweise gut ab. Diese Methode verarbeitete die 100.000 Zeilen in 165 ms, also etwa doppelt so schnell wie die String Primitives in Python.
Fazit
Das Ergebnis ist, dass reguläre Ausdrücke in der Tat deutlich langsamer sind als einfache String Primitives. Das Ausmaß des Unterschieds hängt jedoch stark von der Programmiersprache ab, die Sie verwenden.
Wenn Sie außerdem hochleistungsfähigen Code zur Ersetzung von Strings schreiben, sind kompilierte reguläre Ausdrücke die richtige Wahl.