java.util.regex - importanza di Pattern.compile ()?


118

Qual è l'importanza del Pattern.compile()metodo?
Perché devo compilare la stringa regex prima di ottenere l' Matcheroggetto?

Per esempio :

String regex = "((\\S+)\\s*some\\s*";

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);

2
Bene, l'importanza è quasi NESSUNA se l'implementazione (come in JDK 1.7) è solo una semplice SCORCIATOIA al nuovo Pattern (regex, 0); Detto questo, la VERA importanza non è il metodo statico in sé, ma la creazione e la restituzione di un nuovo Pattern che può essere salvato per quest'ultimo utilizzo. Forse ci sono altre implementazioni in cui il metodo statico prende una nuova rotta e memorizza nella cache gli oggetti Pattern, e questo sarebbe un vero caso di importanza di Pattern.compile ()!
marcolopes

Le risposte evidenziano l'importanza di separare pattern e classi di corrispondenza (che è probabilmente ciò che la domanda chiede), ma nessuno risponde perché non possiamo semplicemente usare un costruttore new Pattern(regex)invece di una funzione di compilazione statica. Il commento di Marcolopes è a posto.
kon psych

Risposte:


144

Il compile()metodo viene sempre chiamato a un certo punto; è l'unico modo per creare un oggetto Pattern. Quindi la domanda è davvero, perché dovresti chiamarla esplicitamente ? Uno dei motivi è che è necessario un riferimento all'oggetto Matcher in modo da poter utilizzare i suoi metodi, ad esempio group(int)per recuperare il contenuto dei gruppi di acquisizione. L'unico modo per ottenere l'oggetto Matcher è tramite il matcher()metodo dell'oggetto Pattern e l'unico modo per ottenere l'oggetto Pattern è attraverso il compile()metodo. Poi c'è il find()metodo che, a differenza matches(), non è duplicato nelle classi String o Pattern.

L'altro motivo è evitare di creare continuamente lo stesso oggetto Pattern. Ogni volta che usi uno dei metodi basati su espressioni regolari in String (o il matches()metodo statico in Pattern), crea un nuovo Pattern e un nuovo Matcher. Quindi questo snippet di codice:

for (String s : myStringList) {
    if ( s.matches("\\d+") ) {
        doSomething();
    }
}

... è esattamente equivalente a questo:

for (String s : myStringList) {
    if ( Pattern.compile("\\d+").matcher(s).matches() ) {
        doSomething();
    }
}

Ovviamente, sta facendo un sacco di lavoro non necessario. In effetti, può facilmente richiedere più tempo per compilare la regex e istanziare l'oggetto Pattern, piuttosto che per eseguire una corrispondenza effettiva. Quindi di solito ha senso tirare quel passo fuori dal giro. Puoi anche creare il Matcher in anticipo, anche se non sono così costosi:

Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("");
for (String s : myStringList) {
    if ( m.reset(s).matches() ) {
        doSomething();
    }
}

Se hai familiarità con le regex .NET, potresti chiederti se il compile()metodo di Java è correlato al RegexOptions.Compiledmodificatore di .NET ; la risposta è no. Il Pattern.compile()metodo di Java è semplicemente equivalente al costruttore Regex di .NET. Quando specifichi l' Compiledopzione:

Regex r = new Regex(@"\d+", RegexOptions.Compiled); 

... compila la regex direttamente in codice byte CIL, permettendogli di eseguire molto più velocemente, ma a un costo significativo nell'elaborazione iniziale e nell'uso della memoria: pensala come steroidi per le regex. Java non ha equivalenti; non c'è differenza tra un Pattern creato dietro le quinte da String#matches(String)e uno con cui crei esplicitamente Pattern#compile(String).

(EDIT: originariamente ho detto che tutti gli oggetti .NET Regex sono memorizzati nella cache, il che non è corretto. Dal momento che .NET 2.0, la memorizzazione automatica nella cache si verifica solo con metodi statici come Regex.Matches(), non quando chiami direttamente un costruttore Regex. Ref )


1
Tuttavia, questo non spiega l'importanza di un tale metodo TRIVIAL sulla classe Pattern! Ho sempre pensato che il metodo statico Pattern.compile fosse molto più di una semplice SCORCIATOIA a new Pattern (regex, 0); Mi aspettavo un CACHE di schemi compilati ... mi sbagliavo. Forse creare una cache è più costoso che creare nuovi modelli ??!
marcolopes

9
Tieni presente che la classe Matcher non è thread-safe e non dovrebbe essere condivisa tra thread. D'altra parte Pattern.compile () lo è.
gswierczynski

1
TLDR; "... [Pattern.compile (...)] compila la regex direttamente in codice byte CIL, permettendogli di eseguire molto più velocemente, ma a un costo significativo nell'elaborazione iniziale e nell'uso della memoria"
sean.boyer

3
Anche se è vero che i Matcher non sono così costosi come Pattern.compile, ho eseguito alcune metriche in uno scenario in cui si stavano verificando migliaia di corrispondenze regex e c'era un ulteriore risparmio molto significativo creando il Matcher in anticipo e riutilizzandolo tramite Matcher .Ripristina(). Evitare la creazione di nuovi oggetti nell'heap in metodi chiamati migliaia di volte è solitamente molto più leggero su CPU, memoria e quindi GC.
Volksman

@Volksman che non è un consiglio generale sicuro perché gli oggetti Matcher non sono threadsafe. Inoltre non è rilevante per la domanda. Ma sì, potresti resetcreare un oggetto Matcher che viene utilizzato solo da un thread alla volta per ridurre le allocazioni.
AndrewF

40

Compile analizza l'espressione regolare e crea una rappresentazione in memoria . Il sovraccarico per la compilazione è significativo rispetto a una corrispondenza. Se stai usando un pattern ripetutamente , otterrai delle prestazioni per memorizzare nella cache il pattern compilato.


7
Inoltre puoi specificare flag come case_insensitive, dot_all, ecc. Durante la compilazione, passando un parametro flag extra
Sam Barnum

17

Quando si compila, PatternJava esegue alcuni calcoli per trovare le corrispondenze inString più veloce la s. (Costruisce una rappresentazione in memoria della regex)

Se hai intenzione di riutilizzare Patternpiù volte, vedresti un notevole aumento delle prestazioni rispetto alla creazione di un nuovoPattern ogni volta.

Nel caso in cui si utilizzi il Pattern una sola volta, il passaggio di compilazione sembra solo una riga di codice aggiuntiva, ma, in effetti, può essere molto utile nel caso generale.


5
Ovviamente puoi scrivere tutto in una riga Matcher matched = Pattern.compile(regex).matcher(text);. Ci sono vantaggi in questo rispetto all'introduzione di un singolo metodo: gli argomenti sono effettivamente denominati ed è ovvio come fattorizzare Patternper prestazioni migliori (o suddividere i metodi).
Tom Hawtin - tackline

1
Sembra sempre che tu sappia così tanto su Java. Dovrebbero assumerti per lavorare per loro ...
jjnguy

5

È questione di prestazioni e utilizzo della memoria, compilare e mantenere il modello rispettato se è necessario utilizzarlo molto. Un utilizzo tipico di regex è quello di convalidare l' input dell'utente (formato) e anche di formattare i dati di output per gli utenti , in queste classi, il salvataggio del pattern rispettato, sembra abbastanza logico in quanto di solito chiamano molto.

Di seguito è riportato un validatore di esempio, che è davvero chiamato molto :)

public class AmountValidator {
    //Accept 123 - 123,456 - 123,345.34
    private static final String AMOUNT_REGEX="\\d{1,3}(,\\d{3})*(\\.\\d{1,4})?|\\.\\d{1,4}";
    //Compile and save the pattern  
    private static final Pattern AMOUNT_PATTERN = Pattern.compile(AMOUNT_REGEX);


    public boolean validate(String amount){

         if (!AMOUNT_PATTERN.matcher(amount).matches()) {
            return false;
         }    
        return true;
    }    
}

Come menzionato da @Alan Moore, se hai regex riutilizzabile nel tuo codice, (prima di un ciclo per esempio), devi compilare e salvare il pattern per il riutilizzo.


2

Pattern.compile()consentire di riutilizzare una regex più volte (è threadsafe). Il vantaggio in termini di prestazioni può essere piuttosto significativo.

Ho fatto un rapido benchmark:

    @Test
    public void recompile() {
        var before = Instant.now();
        for (int i = 0; i < 1_000_000; i++) {
            Pattern.compile("ab").matcher("abcde").matches();
        }
        System.out.println("recompile " + Duration.between(before, Instant.now()));
    }

    @Test
    public void compileOnce() {
        var pattern = Pattern.compile("ab");
        var before = Instant.now();
        for (int i = 0; i < 1_000_000; i++) {
            pattern.matcher("abcde").matches();
        }
        System.out.println("compile once " + Duration.between(before, Instant.now()));
    }

compileOnce era tra 3x e 4x più veloce . Immagino che dipenda molto dalla regex stessa, ma per una regex che viene spesso utilizzata, scelgo un filestatic Pattern pattern = Pattern.compile(...)


0

La precompilazione della regex aumenta la velocità. Riutilizzare il Matcher ti dà un'altra leggera accelerazione. Se il metodo viene chiamato frequentemente, ad esempio viene chiamato all'interno di un ciclo, le prestazioni complessive aumenteranno sicuramente.


0

Simile a 'Pattern.compile' c'è 'RECompiler.compile' [da com.sun.org.apache.regexp.internal] dove:
1. codice compilato per pattern [az] contiene 'az'
2. codice compilato per il motivo [0-9] contiene "09"
3. il codice compilato per pattern [abc] contiene "aabbcc".

Il codice compilato è quindi un ottimo modo per generalizzare più casi. Quindi, invece di avere una diversa situazione di gestione del codice 1,2 e 3. Il problema si riduce al confronto con gli ascii dell'elemento presente e successivo nel codice compilato, da cui le coppie. Quindi
a. qualsiasi cosa con ascii tra a e z è compresa tra a e z
b. qualsiasi cosa con ascii tra 'a e a è sicuramente' a '


0

La classe Pattern è il punto di ingresso del motore regex. Puoi usarla tramite Pattern.matches () e Pattern.comiple (). #Differenza tra questi due. corrisponde () - per controllare rapidamente se un testo (stringa) corrisponde a una data espressione regolare comiple () - crea il riferimento di Pattern. Quindi può essere utilizzato più volte per abbinare l'espressione regolare a più testi.

Per riferimento:

public static void main(String[] args) {
     //single time uses
     String text="The Moon is far away from the Earth";
     String pattern = ".*is.*";
     boolean matches=Pattern.matches(pattern,text);
     System.out.println("Matches::"+matches);

    //multiple time uses
     Pattern p= Pattern.compile("ab");
     Matcher  m=p.matcher("abaaaba");
     while(m.find()) {
         System.out.println(m.start()+ " ");
     }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.