Pre-kompilere Regex-mønstre til mønsterobjekter

1. Oversigt

I denne vejledning ser vi fordelene ved at præ-kompilere et regex-mønster og nye metoder introduceret i Java 8 og 11.

Dette vil ikke være en regex-vejledning, men vi har en fremragende guide til Java Regular Expressions API til dette formål.

2. Fordele

Genbrug bringer uundgåeligt præstationsgevinst, da vi ikke har brug for at oprette og genskabe forekomster af de samme objekter gang på gang. Så vi kan antage, at genbrug og ydeevne ofte er forbundet.

Lad os se på dette princip, som det vedrører Mønster # kompilere. We bruger et simpelt benchmark:

  1. Vi har en liste med 5.000.000 numre fra 1 til 5.000.000
  2. Vores regex matcher lige tal

Så lad os teste parsing af disse tal med følgende Java regex-udtryk:

  • String.matches (regex)
  • Pattern.matches (regex, charSequence)
  • Pattern.compile (regex) .matcher (charSequence) .matches ()
  • Præ-kompileret regex med mange opkald til preCompiledPattern.matcher (værdi) .matches ()
  • Forud kompileret regex med en Matcher eksempel og mange opkald til matcherFromPreCompiledPattern.reset (værdi) .matches ()

Faktisk, hvis vi ser på String # matches'S implementering:

offentlige boolske kampe (String regex) {return Pattern.matches (regex, dette); }

Og kl Mønster # matches:

offentlige statiske boolske matches (String regex, CharSequence input) {Mønster p = kompilering (regex); Matcher m = s. Matcher (input); returnere m.matches (); }

Så kan vi forestille os det de tre første udtryk fungerer ens. Det skyldes, at det første udtryk kalder det andet, og det andet kalder det tredje.

Det andet punkt er, at disse metoder ikke genbruger Mønster og Matcher forekomster oprettet. Og som vi vil se i benchmarket, dette nedbryder ydeevnen med en faktor på seks:

 @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) {Instant start = Instant.now (); for (Stringværdi: værdier) {bh.consume (value.matches (PATTERN)); }} 

Ser man på benchmarkresultaterne, er der ingen tvivl om det præ-kompileret Mønster og genbruges Matcher er vinderne med et resultat på mere end seks gange hurtigere:

Benchmark tilstand Cnt Score Fejl Units PatternPerformanceComparison.matcherFromPreCompiledPatternResetMatches avgt 20 278,732 ± 22,960 ms / OP PatternPerformanceComparison.preCompiledPatternMatcherMatches avgt 20 500,393 ± 34,182 ms / OP PatternPerformanceComparison.stringMatchs avgt 20 1433,099 ± 73,687 ms / OP PatternPerformanceComparison.patternCompileMatcherMatches avgt 20 1774,429 ± 174,955 ms / op MønsterPerformanceComparison.patternMatches avgt 20 1792.874 ± 130,213 ms / op

Ud over præstationstider har vi også antallet af oprettede objekter:

  • De første tre former:
    • 5,000,000 Mønster forekomster oprettet
    • 5,000,000 Matcher forekomster oprettet
  • preCompiledPattern.matcher (værdi) .matches ()
    • 1 Mønster instans oprettet
    • 5,000,000 Matcher forekomster oprettet
  • matcherFromPreCompiledPattern.reset (værdi) .matches ()
    • 1 Mønster instans oprettet
    • 1 Matcher instans oprettet

Så i stedet for at delegere vores regex til String # matches eller Mønster # matches der altid vil skabe Mønster og Matcher tilfælde. Vi skal præ-kompilere vores regex for at opnå præstationer og har færre objekter oprettet.

For at vide mere om ydeevne i regex, se vores oversigt over Performance of Regular Expressions i Java.

3. Nye metoder

Siden introduktionen af ​​funktionelle grænseflader og streams er genbrug blevet lettere.

Det Mønster klasse har udviklet sig i nye Java-versioner at give integration med vandløb og lambdas.

3.1. Java 8

Java 8 introducerede to nye metoder: splitAsStream og som prædikat.

Lad os se på en kode til splitAsStream der skaber en stream fra den givne indgangssekvens omkring matches af mønsteret:

@Test offentlig ugyldighed givenPreCompiledPattern_whenCallSplitAsStream_thenReturnArraySplitByThePattern () {Mønster splitPreCompiledPattern = Pattern.compile ("__"); Stream textSplitAsStream = splitPreCompiledPattern.splitAsStream ("My_Name__is__Fabio_Silva"); String [] textSplit = textSplitAsStream.toArray (String [] :: new); assertEquals ("My_Name", textSplit [0]); assertEquals ("er", textSplit [1]); assertEquals ("Fabio_Silva", textSplit [2]); }

Det som prædikat metoden opretter et prædikat, der opfører sig som om det skaber en matcher fra indgangssekvensen og derefter kald finder:

streng -> matcher (streng) .find ();

Lad os oprette et mønster, der matcher navne fra en liste, der har mindst for- og efternavne med mindst tre bogstaver hver:

@Test offentlig ugyldighed givenPreCompiledPattern_whenCallAsPredicate_thenReturnPredicateToFindPatternInTheList () {List namesToValidate = Arrays.asList ("Fabio Silva", "Mr. Silva"); Mønster firstLastNamePreCompiledPattern = Mønster.kompil ("[a-zA-Z] {3,} [a-zA-Z] {3,}"); Predikat mønstreAsPredicate = firstLastNamePreCompiledPattern.asPredicate (); Liste validNames = namesToValidate.stream () .filter (patternsAsPredicate) .collect (Collectors.toList ()); assertEquals (1, validNames.size ()); assertTrue (validNames.contains ("Fabio Silva")); }

3.2. Java 11

Java 11 introducerede asMatchPredicate metode der skaber et prædikat, der opfører sig som om det opretter en matcher fra indgangssekvensen og derefter kalder matches:

streng -> matcher (streng) .matches ();

Lad os oprette et mønster, der matcher navne fra en liste, der kun har for- og efternavn med mindst tre bogstaver hver:

@Test offentlig ugyldighed givenPreCompiledPattern_whenCallAsMatchPredicate_thenReturnMatchPredicateToMatchesPattern () {List namesToValidate = Arrays.asList ("Fabio Silva", "Fabio Luis Silva"); Mønster firstLastNamePreCompiledPattern = Mønster.kompil ("[a-zA-Z] {3,} [a-zA-Z] {3,}"); PredikatønsterAsMatchPredicate = firstLastNamePreCompiledPattern.asMatchPredicate (); Liste validatedNames = namesToValidate.stream () .filter (patternAsMatchPredicate) .collect (Collectors.toList ()); assertTrue (validatedNames.contains ("Fabio Silva")); assertFalse (validatedNames.contains ("Fabio Luis Silva")); }

4. Konklusion

I denne vejledning så vi, at brug af præ-kompilerede mønstre giver os en langt bedre præstation.

Vi lærte også om tre nye metoder introduceret i JDK 8 og JDK 11, der gør vores liv lettere.

Koden til disse eksempler er tilgængelig på GitHub i core-java-11 til JDK 11-uddrag og kerne-java-regex for de andre.