Predkompilujte vzory regulárneho výrazu do vzorových objektov
1. Prehľad
V tomto návode uvidíme výhody predkompilovaného regexového vzoru a nové metódy zavedené v prostredí Java 8 a 11.
Toto nebude postup regulárneho výrazu, ale na tento účel máme vynikajúci Guide to Java Regular Expressions API.
2. Výhody
Opätovné použitie nevyhnutne prináša zvýšenie výkonu, pretože nemusíme opakovane vytvárať a znovu vytvárať inštancie tých istých objektov. Môžeme teda predpokladať, že opätovné použitie a výkon sú často spojené.
Pozrime sa teraz na túto zásadu Vzor # zostaviť. Žpoužijeme jednoduchý benchmark:
- Máme zoznam s 5 000 000 číslami od 1 do 5 000 000
- Náš regulárny výraz bude zodpovedať párnym číslam
Poďme teda vyskúšať parsovanie týchto čísel pomocou nasledujúcich výrazov regexu Java:
- String.matches (regex)
- Pattern.matches (regex, charSequence)
- Pattern.compile (regex) .matcher (charSequence) .matches ()
- Predkompilovaný regulárny výraz s množstvom volaní na preCompiledPattern.matcher (hodnota) .matches ()
- Predkompilovaný regulárny výraz s jedným Matcher napríklad a veľa hovorov na matcherFromPreCompiledPattern.reset (hodnota) .matches ()
Vlastne, keď sa pozrieme na Reťazec # zápasovImplementácia:
verejné boolovské zhody (reťazec regex) {návrat Pattern.matches (regex, toto); }
A o Vzorec # sa zhoduje:
verejné statické booleovské zhody (reťazcový regex, vstup CharSequence) {vzor p = kompilácia (regex); Matcher m = p.matcher (vstup); vrátiť m.matches (); }
Potom si to vieme predstaviť prvé tri výrazy budú mať podobné výsledky. Je to preto, že prvý výraz volá druhý a druhý volá tretí.
Druhým bodom je, že tieto metódy opätovne nepoužívajú Vzor a Matcher inštancie vytvorené. A ako uvidíme v referenčnej hodnote, to znižuje výkon šesťnásobne:
@Benchmark public void matcherFromPreCompiledPatternResetMatches (Blackhole bh) {for (String value: values) {bh.consume (matcherFromPreCompiledPattern.reset (value) .matches ()); }} @Benchmark public void preCompiledPatternMatcherMatches (Blackhole bh) {for (String value: values) {bh.consume (preCompiledPattern.matcher (value) .matches ()); }} @Benchmark public void patternCompileMatcherMatches (Blackhole bh) {for (String value: values) {bh.consume (Pattern.compile (PATTERN) .matcher (value) .matches ()); }} @Benchmark public void patternMatches (Blackhole bh) {for (String value: values) {bh.consume (Pattern.matches (PATTERN, value)); }} @Benchmark public void stringMatchs (Blackhole bh) {Okamžitý štart = Instant.now (); pre (Reťazcová hodnota: hodnoty) {bh.consume (value.matches (PATTERN)); }}
Pri pohľade na referenčné výsledky nie je pochýb o tom predkompilovaný Vzor a znovu použité Matcher sú víťazmi s výsledkom viac ako šesťkrát rýchlejším:
Porovnávací režim Cnt Skóre Chybové jednotky PatternPerformanceComparison.matcherFromPreCompiledPatternResetMatches avgt 20 278,732 ± 22,960 ms / P PatternPerformanceComparison.preCompiledPatternMatcherMatches avgt 20 500,393 ± 34,182 ms / P PatternPerformanceComparison.stringMatchs avgt 20 1433,099 ± 73,687 ms / P PatternPerformanceComparison.patternCompileMatcherMatches avgt 20 1774,429 ± 174,955 ms / op PatternPerformanceComparison.patternZhody priem. 20 1792,874 ± 130,213 ms / op
Okrem časov výkonu máme aj počet vytvorených objektov:
- Prvé tri formy:
- 5,000,000 Vzor inštancie vytvorené
- 5,000,000 Matcher inštancie vytvorené
- preCompiledPattern.matcher (hodnota) .matches ()
- 1 Vzor inštancia bola vytvorená
- 5,000,000 Matcher inštancie vytvorené
- matcherFromPreCompiledPattern.reset (hodnota) .matches ()
- 1 Vzor inštancia bola vytvorená
- 1 Matcher inštancia bola vytvorená
Namiesto delegovania nášho regulárneho výrazu na Reťazec # zápasov alebo Vzorec # sa zhoduje že vždy vytvorí Vzor a Matcher inštancie. Mali by sme vopred skompilovať náš regulárny výraz, aby sme dosiahli výkon a bolo vytvorených menej objektov.
Ak sa chcete dozvedieť viac informácií o výkone v regulárnom jazyku, pozrite si náš prehľad výkonu regulárnych výrazov v jazyku Java.
3. Nové metódy
Od zavedenia funkčných rozhraní a streamov je opätovné použitie jednoduchšie.
The Vzor trieda sa vyvinula v nových verziách Java zabezpečiť integráciu s prúdmi a lambdami.
3.1. Java 8
Java 8 predstavila dve nové metódy: splitAsStream a akoPredikát.
Pozrime sa na nejaký kód pre splitAsStream ktorý vytvorí prúd z danej vstupnej sekvencie okolo zhôd vzoru:
@Test public void givenPreCompiledPattern_whenCallSplitAsStream_thenReturnArraySplitByThePattern () {Pattern splitPreCompiledPattern = Pattern.compile ("__"); Streamovať textSplitAsStream = splitPreCompiledPattern.splitAsStream ("My_Name__is__Fabio_Silva"); Reťazec [] textSplit = textSplitAsStream.toArray (Reťazec [] :: nový); assertEquals ("My_Name", textSplit [0]); assertEquals ("je", textSplit [1]); assertEquals ("Fabio_Silva", textSplit [2]); }
The akoPredikát metóda vytvorí predikát, ktorý sa chová, akoby vytvoril zo vstupnej sekvencie porovnávač a potom zavolá find:
string -> matcher (string) .find ();
Vytvorme vzor, ktorý sa zhoduje s menami zo zoznamu, ktorý má aspoň meno a priezvisko a každé má aspoň tri písmená:
@Test public void givenPreCompiledPattern_whenCallAsPredicate_thenReturnPredicateToFindPatternInTheList () {List namesToValidate = Arrays.asList ("Fabio Silva", "Mr. Silva"); Pattern firstLastNamePreCompiledPattern = Pattern.compile ("[a-zA-Z] {3,} [a-zA-Z] {3,}"); Predikátové vzoryAsPredicate = firstLastNamePreCompiledPattern.asPredicate (); Zoznam validNames = namesToValidate.stream () .filter (vzoryAsPredicate) .collect (Collectors.toList ()); assertEquals (1, validNames.size ()); assertTrue (validNames.contains ("Fabio Silva")); }
3.2. Java 11
Java 11 predstavila asMatchPredicate metóda , ktorý vytvorí predikát, ktorý sa chová, akoby vytvoril zo vstupnej sekvencie porovnávač a potom zavolá zhody:
string -> matcher (string) .matches ();
Vytvorme vzor, ktorý sa zhoduje s menami zo zoznamu, ktorý má iba meno a priezvisko a každé má najmenej tri písmená:
@Test public void givenPreCompiledPattern_whenCallAsMatchPredicate_thenReturnMatchPredicateToMatchesPattern () {List namesToValidate = Arrays.asList ("Fabio Silva", "Fabio Luis Silva"); Pattern firstLastNamePreCompiledPattern = Pattern.compile ("[a-zA-Z] {3,} [a-zA-Z] {3,}"); Predikát patternAsMatchPredicate = firstLastNamePreCompiledPattern.asMatchPredicate (); Zoznam validatedNames = namesToValidate.stream () .filter (patternAsMatchPredicate) .collect (Collectors.toList ()); assertTrue (validatedNames.contains ("Fabio Silva")); assertFalse (validatedNames.contains ("Fabio Luis Silva")); }
4. Záver
V tomto tutoriáli sme videli, že použitie predkompilovaných vzorov nám prináša oveľa lepší výkon.
Dozvedeli sme sa aj o troch nové metódy zavedené v JDK 8 a JDK 11, ktoré uľahčujú náš život.
Kód týchto príkladov je k dispozícii na GitHub v jadro-java-11 pre úryvky JDK 11 a jadro-java-regex pre ostatných.