Pra-kompilasi Pola Regex Menjadi Objek Pola

1. Ikhtisar

Dalam tutorial ini, kita akan melihat manfaat dari pra-kompilasi pola regex dan metode baru yang diperkenalkan di Java 8 dan 11 .

Ini bukan cara regex, tetapi kami memiliki Panduan yang sangat baik untuk Java Regular Expressions API untuk tujuan itu.

2. Manfaat

Penggunaan kembali pasti membawa peningkatan kinerja, karena kita tidak perlu membuat dan membuat ulang contoh objek yang sama dari waktu ke waktu. Jadi, kita dapat berasumsi bahwa penggunaan kembali dan kinerja sering kali terkait.

Mari kita lihat prinsip ini karena berkaitan dengan Pattern # compile. W e'll menggunakan patokan sederhana :

  1. Kami memiliki daftar dengan 5.000.000 nomor dari 1 hingga 5.000.000
  2. Regex kami akan cocok dengan angka genap

Jadi, mari kita uji parsing angka-angka ini dengan ekspresi regex Java berikut:

  • String.matches (regex)
  • Pattern.matches (regex, charSequence)
  • Pattern.compile (regex) .matcher (charSequence) .matches ()
  • Regex yang telah dikompilasi sebelumnya dengan banyak panggilan ke preCompiledPattern.matcher (value) .matches ()
  • Regex yang telah dikompilasi sebelumnya dengan satu instance Matcher dan banyak panggilan ke matcherFromPreCompiledPattern.reset (value) .matches ()

Sebenarnya, jika kita melihat implementasi string # Matches :

public boolean matches(String regex) { return Pattern.matches(regex, this); }

Dan pada pola # pertandingan :

public static boolean matches(String regex, CharSequence input) { Pattern p = compile(regex); Matcher m = p.matcher(input); return m.matches(); }

Kemudian, kita dapat membayangkan bahwa tiga ekspresi pertama akan tampil serupa. Itu karena ekspresi pertama memanggil yang kedua, dan yang kedua memanggil yang ketiga.

Poin kedua adalah bahwa metode ini tidak menggunakan kembali contoh Pattern dan Matcher yang dibuat. Dan, seperti yang akan kita lihat di tolok ukur, ini menurunkan kinerja dengan faktor enam :

 @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 (String value : values) { bh.consume(value.matches(PATTERN)); } } 

Melihat hasil benchmark, tidak ada keraguan bahwa Pattern yang telah dikompilasi dan Matcher yang digunakan kembali adalah pemenang dengan hasil lebih dari enam kali lebih cepat :

Benchmark Mode Cnt Score Error 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 PatternPerformanceComparison.patternMatches avgt 20 1792.874 ± 130.213 ms/op

Di luar waktu kinerja, kami juga memiliki jumlah objek yang dibuat :

  • Tiga bentuk pertama:
    • 5.000.000 instance Pola dibuat
    • 5.000.000 instance Matcher dibuat
  • preCompiledPattern.matcher (nilai) .matches ()
    • 1 Contoh pola dibuat
    • 5.000.000 instance Matcher dibuat
  • matcherFromPreCompiledPattern.reset (nilai) .matches ()
    • 1 Contoh pola dibuat
    • 1 instance Matcher dibuat

Jadi, alih-alih mendelegasikan regex kami ke kecocokan String # atau Pattern # yang selalu akan membuat instance Pattern dan Matcher . Kita harus melakukan pra-kompilasi regex kita untuk mendapatkan performa dan memiliki lebih sedikit objek yang dibuat.

Untuk mengetahui lebih lanjut tentang performa di regex, lihat Ikhtisar Performa Ekspresi Reguler kami di Java.

3. Metode Baru

Sejak diperkenalkannya antarmuka dan aliran fungsional, penggunaan kembali menjadi lebih mudah.

Kelas Pola telah berevolusi dalam versi Java baru untuk menyediakan integrasi dengan aliran dan lambda.

3.1. Jawa 8

Java 8 memperkenalkan dua metode baru: splitAsStream dan asPredicate .

Mari kita lihat beberapa kode untuk splitAsStream yang membuat aliran dari urutan masukan yang diberikan di sekitar pola yang cocok:

@Test public void givenPreCompiledPattern_whenCallSplitAsStream_thenReturnArraySplitByThePattern() { Pattern splitPreCompiledPattern = Pattern.compile("__"); Stream textSplitAsStream = 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]); }

The asPredicate Metode menciptakan predikat yang berperilaku seolah-olah itu menciptakan matcher dari urutan input dan kemudian memanggil menemukan:

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

Mari buat pola yang cocok dengan nama dari daftar yang memiliki setidaknya nama depan dan belakang dengan masing-masing setidaknya tiga huruf:

@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,}"); Predicate patternsAsPredicate = firstLastNamePreCompiledPattern.asPredicate(); List validNames = namesToValidate.stream() .filter(patternsAsPredicate) .collect(Collectors.toList()); assertEquals(1,validNames.size()); assertTrue(validNames.contains("Fabio Silva")); }

3.2. Jawa 11

Java 11 memperkenalkan metode asMatchPredicate yang membuat predikat yang berperilaku seolah-olah membuat matcher dari urutan masukan dan kemudian memanggil kecocokan:

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

Mari buat pola yang cocok dengan nama dari daftar yang hanya memiliki nama depan dan belakang dengan masing-masing setidaknya tiga huruf:

@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,}"); Predicate patternAsMatchPredicate = firstLastNamePreCompiledPattern.asMatchPredicate(); List validatedNames = namesToValidate.stream() .filter(patternAsMatchPredicate) .collect(Collectors.toList()); assertTrue(validatedNames.contains("Fabio Silva")); assertFalse(validatedNames.contains("Fabio Luis Silva")); }

4. Kesimpulan

Dalam tutorial ini, kami melihat bahwa penggunaan pola yang telah dikompilasi sebelumnya memberi kami kinerja yang jauh lebih unggul .

Kami juga belajar tentang tiga metode baru yang diperkenalkan di JDK 8 dan JDK 11 yang membuat hidup kita lebih mudah .

Kode untuk contoh ini tersedia di GitHub di core-java-11 untuk cuplikan JDK 11 dan core-java-regex untuk yang lainnya.