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:

  1. Máme zoznam s 5 000 000 číslami od 1 do 5 000 000
  2. 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.


$config[zx-auto] not found$config[zx-overlay] not found