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

1. Oversikt

I denne opplæringen ser vi fordelene med å pre-kompilere et regex-mønster og nye metoder introdusert i Java 8 og 11.

Dette vil ikke være en regex-hvordan, men vi har en utmerket guide til Java Regular Expressions API for det formålet.

2. Fordeler

Gjenbruk gir uunngåelig gevinst, siden vi ikke trenger å lage og gjenskape forekomster av de samme objektene gang på gang. Så vi kan anta at gjenbruk og ytelse ofte er knyttet sammen.

La oss se på dette prinsippet slik det gjelder Mønster # kompiler. WJeg bruker en enkel referanse:

  1. Vi har en liste med 5.000.000 tall fra 1 til 5.000.000
  2. Regexen vår vil matche partall

Så la oss teste parsing av disse tallene med følgende Java regex-uttrykk:

  • String.matches (regex)
  • Pattern.matches (regex, charSequence)
  • Pattern.compile (regex) .matcher (charSequence) .matches ()
  • Pre-compiled regex med mange samtaler til preCompiledPattern.matcher (verdi) .matches ()
  • Pre-compiled regex with one Matcher forekomst og mange samtaler til matcherFromPreCompiledPattern.reset (verdi) .matches ()

Egentlig, hvis vi ser på Streng # treff’Implementering:

offentlige boolske kamper (String regex) {return Pattern.matches (regex, this); }

Og kl Mønster # samsvarer:

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

Så kan vi forestille oss det de tre første uttrykkene vil fungere på samme måte. Det er fordi det første uttrykket kaller det andre, og det andre kaller det tredje.

Det andre poenget er at disse metodene ikke gjenbruker Mønster og Matcher forekomster opprettet. Og som vi vil se i referanseindeksen, dette nedbryter ytelsen med en faktor på seks:

 @Benchmark public void matcherFromPreCompiledPatternResetMatches (Blackhole bh) {for (Strengverdi: verdier) {bh.consume (matcherFromPreCompiledPattern.reset (verdi) .matches ()); }} @Benchmark public void preCompiledPatternMatcherMatches (Blackhole bh) {for (Strengverdi: verdier) {bh.consume (preCompiledPattern.matcher (verdi) .matches ()); }} @Benchmark public void patternCompileMatcherMatches (Blackhole bh) {for (Strengverdi: verdier) {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 (Strengverdi: verdier) {bh.consume (value.matches (PATTERN)); }} 

Ser man på referanseresultatene, er det ingen tvil om det ferdig kompilert Mønster og gjenbrukt Matcher er vinnerne med et resultat på mer enn seks ganger raskere:

Referanse Mode Cnt Score Feilvogner 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

Utover ytelsestider har vi også antall objekter opprettet:

  • De tre første skjemaene:
    • 5,000,000 Mønster forekomster opprettet
    • 5,000,000 Matcher forekomster opprettet
  • preCompiledPattern.matcher (verdi) .matches ()
    • 1 Mønster forekomst opprettet
    • 5,000,000 Matcher forekomster opprettet
  • matcherFromPreCompiledPattern.reset (verdi) .matches ()
    • 1 Mønster forekomst opprettet
    • 1 Matcher forekomst opprettet

Så i stedet for å delegere regexen vår til Streng # treff eller Mønster # samsvarer som alltid vil skape Mønster og Matcher tilfeller. Vi bør forhåndskompilere vår regex for å oppnå ytelse og har færre objekter opprettet.

Hvis du vil vite mer om ytelse i regex, kan du se vår oversikt over ytelse for regulære uttrykk i Java.

3. Nye metoder

Siden introduksjonen av funksjonelle grensesnitt og strømmer har gjenbruk blitt enklere.

De Mønster klasse har utviklet seg i nye Java-versjoner for å gi integrasjon med bekker og lambdas.

3.1. Java 8

Java 8 introduserte to nye metoder: splitAsStream og asPredicate.

La oss se på noe kode for splitAsStream som skaper en strøm fra den gitte inngangssekvensen rundt treff i mønsteret:

@Test offentlig ugyldig gittPreCompiledPattern_whenCallSplitAsStream_thenReturnArraySplitByThePattern () {Mønster splitPreCompiledPattern = Pattern.compile ("__"); Strøm tekstSplitAsStream = splitPreCompiledPattern.splitAsStream ("My_Name__is__Fabio_Silva"); String [] textSplit = textSplitAsStream.toArray (String [] :: new); assertEquals ("My_Name", textSplit [0]); assertEquals ("is", textSplit [1]); assertEquals ("Fabio_Silva", textSplit [2]); }

De asPredicate metoden oppretter et predikat som oppfører seg som om det skaper en matcher fra inngangssekvensen og deretter samtaler finner:

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

La oss lage et mønster som samsvarer med navn fra en liste som har minst for- og etternavn med minst tre bokstaver hver:

@Test offentlig ugyldighet gittPreCompiledPattern_whenCallAsPredicate_thenReturnPredicateToFindPatternInTheList () {List namesToValidate = Arrays.asList ("Fabio Silva", "Mr. Silva"); Mønster firstLastNamePreCompiledPattern = Mønster.kompil ("[a-zA-Z] {3,} [a-zA-Z] {3,}"); Predikere 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 introduserte asMatchPredicate metode som skaper et predikat som oppfører seg som om det lager en matcher fra inngangssekvensen og deretter kaller treff:

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

La oss lage et mønster som samsvarer med navn fra en liste som bare har for- og etternavn med minst tre bokstaver hver:

@Test offentlig ugyldighet gittPreCompiledPattern_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,}"); Prediker mønsterAsMatchPredicate = firstLastNamePreCompiledPattern.asMatchPredicate (); Liste validatedNames = namesToValidate.stream () .filter (patternAsMatchPredicate) .collect (Collectors.toList ()); assertTrue (validatedNames.contains ("Fabio Silva")); assertFalse (validatedNames.contains ("Fabio Luis Silva")); }

4. Konklusjon

I denne veiledningen så vi at bruk av pre-kompilerte mønstre gir oss en langt overlegen ytelse.

Vi lærte også om tre nye metoder introdusert i JDK 8 og JDK 11 som gjør livet vårt enklere.

Koden for disse eksemplene er tilgjengelig på GitHub i core-java-11 for JDK 11-utdragene og core-java-regex for de andre.


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