java.util.regex - importance de Pattern.compile ()?

118

Quelle est l'importance de la Pattern.compile()méthode?
Pourquoi dois-je compiler la chaîne de regex avant d'obtenir l' Matcherobjet?

Par exemple :

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

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);
Sidharth
la source
2
Eh bien, l'importance est presque NONE si l'implémentation (comme dans JDK 1.7) n'est qu'un simple RACCOURCI vers un nouveau Pattern (regex, 0); Cela dit, l'importance RÉELLE n'est pas la méthode statique elle-même, mais la création et le retour d'un nouveau Pattern qui peut être sauvegardé pour une utilisation ultérieure. Peut-être qu'il y a d'autres implémentations où la méthode statique prend une nouvelle route et met en cache les objets Pattern, et ce serait un cas réel d'importance Pattern.compile ()!
marcolopes
Les réponses soulignent l'importance de séparer les modèles et les classes correspondantes (ce qui est probablement ce que la question pose), mais personne ne répond pourquoi nous ne pouvons pas simplement utiliser un constructeur new Pattern(regex)au lieu d'une fonction de compilation statique. Le commentaire des marcolopes est sur place.
kon psych

Réponses:

144

La compile()méthode est toujours appelée à un moment donné; c'est la seule façon de créer un objet Pattern. La question est donc vraiment, pourquoi devriez-vous l'appeler explicitement ? L'une des raisons est que vous avez besoin d'une référence à l'objet Matcher afin de pouvoir utiliser ses méthodes, comme group(int)pour récupérer le contenu des groupes de capture. La seule façon d'obtenir un objet Matcher est d'utiliser la matcher()méthode de l'objet Pattern , et la seule façon d'obtenir un objet Pattern est d'utiliser la compile()méthode. Ensuite, il y a la find()méthode qui, contrairement à matches(), n'est pas dupliquée dans les classes String ou Pattern.

L'autre raison est d'éviter de créer encore et encore le même objet Pattern. Chaque fois que vous utilisez l'une des méthodes basées sur les regex dans String (ou la matches()méthode statique dans Pattern), cela crée un nouveau Pattern et un nouveau Matcher. Donc, cet extrait de code:

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

... est exactement équivalent à ceci:

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

De toute évidence, cela fait beaucoup de travail inutile. En fait, la compilation de l'expression rationnelle et l'instanciation de l'objet Pattern peuvent facilement prendre plus de temps que pour effectuer une correspondance réelle. Il est donc généralement logique de sortir cette étape de la boucle. Vous pouvez également créer le Matcher à l'avance, bien qu'ils ne soient pas si chers:

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

Si vous êtes familier avec les expressions rationnelles .NET, vous vous demandez peut-être si la compile()méthode de Java est liée au RegexOptions.Compiledmodificateur de .NET ; La réponse est non. La Pattern.compile()méthode de Java est simplement équivalente au constructeur Regex de .NET. Lorsque vous spécifiez l' Compiledoption:

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

... il compile le regex directement en code d'octet CIL, ce qui lui permet de fonctionner beaucoup plus rapidement, mais à un coût important en traitement initial et en utilisation de la mémoire - pensez-y comme des stéroïdes pour les expressions régulières. Java n'a pas d'équivalent; il n'y a aucune différence entre un motif créé en coulisse par String#matches(String)et un avec lequel vous créez explicitement Pattern#compile(String).

(EDIT: J'ai dit à l'origine que tous les objets .NET Regex sont mis en cache, ce qui est incorrect. Depuis .NET 2.0, la mise en cache automatique se produit uniquement avec des méthodes statiques comme Regex.Matches(), pas lorsque vous appelez directement un constructeur Regex. Ref )

Alan Moore
la source
1
Pourtant, cela n'explique pas l'importance d'une telle méthode TRIVIAL sur la classe Pattern! J'ai toujours supposé que la méthode statique Pattern.compile était bien plus qu'un simple SHORTCUT vers un nouveau Pattern (regex, 0); Je m'attendais à un CACHE de modèles compilés ... je me suis trompé. Peut-être que créer un cache coûte plus cher que créer de nouveaux modèles ??!
marcolopes
9
Veuillez noter que la classe Matcher n'est pas thread-safe et ne doit pas être partagée entre les threads. D'autre part, Pattern.compile () l'est.
gswierczynski
1
TLDR; "... [Pattern.compile (...)] compile le regex directement en octet CIL, ce qui lui permet de fonctionner beaucoup plus rapidement, mais à un coût important en traitement
initial
3
Bien qu'il soit vrai que les Matchers ne sont pas aussi chers que Pattern.compile, j'ai fait des métriques dans un scénario où des milliers de correspondances regex se produisaient et il y avait une économie supplémentaire très significative en créant le Matcher à l'avance et en le réutilisant via matcher .réinitialiser(). Éviter la création de nouveaux objets dans le tas dans des méthodes appelées des milliers de fois est généralement beaucoup plus léger sur le processeur, la mémoire et donc le GC.
Volksman
@Volksman qui n'est pas un conseil général sûr car les objets Matcher ne sont pas threadsafe. Ce n'est pas non plus pertinent pour la question. Mais oui, vous pourriez resetun objet Matcher qui n'est jamais utilisé par un seul thread à la fois afin de réduire les allocations.
AndrewF
40

Compile analyse l'expression régulière et crée une représentation en mémoire . La surcharge à compiler est significative par rapport à une correspondance. Si vous utilisez un modèle à plusieurs reprises, il gagnera en performances pour mettre en cache le modèle compilé.

Thomas Jung
la source
7
De plus, vous pouvez spécifier des indicateurs tels que case_insensitive, dot_all, etc. lors de la compilation, en passant un paramètre d'indicateurs supplémentaires
Sam Barnum
17

Lorsque vous compilez, PatternJava effectue des calculs pour Stringaccélérer la recherche de correspondances dans s. (Construit une représentation en mémoire de l'expression régulière)

Si vous comptez réutiliser le Pattern plusieurs fois, vous verrez une augmentation considérable des performances par rapport à la création d'un nouveau à Patternchaque fois.

Dans le cas où vous n'utilisez le Pattern qu'une seule fois, l'étape de compilation semble être une ligne de code supplémentaire, mais, en fait, elle peut être très utile dans le cas général.

jjnguy
la source
5
Bien sûr, vous pouvez tout écrire sur une seule ligne Matcher matched = Pattern.compile(regex).matcher(text);. Il y a des avantages à cela par rapport à l'introduction d'une seule méthode: les arguments sont nommés efficacement et il est évident comment prendre en compte les Patternpour de meilleures performances (ou les répartir entre les méthodes).
Tom Hawtin - tackline
1
Il semble que vous en savez toujours autant sur Java. Ils devraient vous embaucher pour travailler pour eux ...
jjnguy
5

C'est une question de performances et d'utilisation de la mémoire, compilez et conservez le modèle respecté si vous avez besoin de l'utiliser beaucoup. Une utilisation typique de l'expression régulière est de valider l' entrée utilisateur (format) , et également de formater les données de sortie pour les utilisateurs , dans ces classes, l'enregistrement du modèle respecté semble assez logique, car ils appellent généralement beaucoup.

Vous trouverez ci-dessous un exemple de validateur, qui s'appelle beaucoup :)

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;
    }    
}

Comme mentionné par @Alan Moore, si vous avez des regex réutilisables dans votre code, (avant une boucle par exemple), vous devez compiler et enregistrer le modèle pour le réutiliser.

Alireza Fattahi
la source
2

Pattern.compile()permet de réutiliser une regex plusieurs fois (c'est threadsafe). L'avantage en termes de performances peut être assez important.

J'ai fait un benchmark rapide:

    @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 était entre 3x et 4x plus rapide . Je suppose que cela dépend fortement de la regex elle-même mais pour une regex qui est souvent utilisée, je vais pour unestatic Pattern pattern = Pattern.compile(...)

apflieger
la source
0

La pré-compilation de l'expression régulière augmente la vitesse. La réutilisation du Matcher vous donne une autre légère accélération. Si la méthode est appelée fréquemment, disons qu'elle est appelée dans une boucle, les performances globales augmenteront certainement.

DragonBorn
la source
0

Similaire à 'Pattern.compile', il y a 'RECompiler.compile' [de com.sun.org.apache.regexp.internal] où:
1. le code compilé pour pattern [az] contient 'az'
2. le code compilé pour le motif [0-9] contient '09'
3. le code compilé pour pattern [abc] a 'aabbcc' dedans.

Ainsi, le code compilé est un excellent moyen de généraliser plusieurs cas. Ainsi au lieu d'avoir des situations de gestion de code différentes 1, 2 et 3. Le problème se réduit à comparer avec l'ascii de l'élément présent et suivant dans le code compilé, d'où les paires. Ainsi
a. tout ce qui a ascii entre a et z est compris entre a et z
b. tout ce qui a ascii entre 'a et a est définitivement' a '

Priyadarshi dévash
la source
0

La classe Pattern est le point d'entrée du moteur regex. Vous pouvez l'utiliser via Pattern.matches () et Pattern.comiple (). # Différence entre ces deux. matches () - pour vérifier rapidement si un texte (String) correspond à une expression régulière donnée comiple () - crée la référence de Pattern. Vous pouvez donc utiliser plusieurs fois pour faire correspondre l'expression régulière à plusieurs textes.

Pour référence:

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()+ " ");
     }
}
vkstream
la source