Présentation des performances des expressions régulières en Java

Présentation des performances des expressions régulières en Java

1. Vue d'ensemble

Dans ce rapide didacticiel, nous allons montrer comment fonctionne le moteur de correspondance de modèles. Nous présenterons également différentes manières d'optimiser lesregular expressions en Java.

Pour une introduction à l'utilisation deregular expressions, veuillez vous référer àto this article here.

2. Le moteur à correspondance

Le packagejava.util.regex utilise un type de moteur de correspondance de modèle appeléNondeterministic Finite Automaton (NFA). It’s considered nondeterministic because while trying to match a regular expression on a given string, each character in the input might be checked several times against different parts of the regular expression.

En arrière-plan, le moteur mentionné ci-dessus utilisebacktracking. Cet algorithme général essaie d’épuiser toutes les possibilités jusqu’à déclarer un échec. Prenons l'exemple suivant pour mieux comprendre lesNFA:

"tra(vel|ce|de)m"

Avec l'entréeString «travel», le moteur cherchera d'abord «tra» et le trouvera immédiatement.

Après cela, il essaiera de faire correspondre «vel» à partir du quatrième caractère. Cela correspondra, donc il ira de l'avant et essaiera de faire correspondre «m».

Cela ne correspondra pas, et pour cette raison, il retournera au quatrième caractère et recherchera "ce". Encore une fois, cela ne correspondra pas, donc il retournera à la quatrième position et essaiera avec "de". Cette chaîne ne correspondra pas non plus, elle retournera donc au deuxième caractère de la chaîne d'entrée et essaiera de rechercher un autre «tra».

Avec le dernier échec, l'algorithme renverra un échec.

Avec le dernier exemple simple, le moteur a dû revenir plusieurs fois en arrière tout en essayant de faire correspondre l'entréeString à l'expression régulière. Pour cette raison,it’s important to minimize the amount of backtracking that it does.

3. Moyens d'optimiser lesRegular Expressions

3.1. Éviter la recompilation

Les expressions régulières en Java sont compilées dans une structure de données interne. Cette compilation est un processus qui prend du temps.

Chaque fois que nous invoquons la méthodeString.matches(String regex) , l'expression régulière spécifiée est recompilée:

if (input.matches(regexPattern)) {
    // do something
}

Comme on peut le constater, chaque fois que la condition est évaluée, l'expression regex est compilée.

Pour optimiser, il est possible de compiler d'abord le modèle, puis de créer unMatcher pour trouver les coïncidences dans la valeur:

Pattern pattern = Pattern.compile(regexPattern);
for(String value : values) {
    Matcher matcher = pattern.matcher(value);
    if (matcher.matches()) {
        // do something
    }
}

Une alternative à l'optimisation ci-dessus consiste à utiliser la même instanceMatcher avec sa méthodereset():

Pattern pattern = Pattern.compile(regexPattern);
Matcher matcher = pattern.matcher("");
for(String value : values) {
    matcher.reset(value);
    if (matcher.matches()) {
      // do something
    }
}

Étant donné queMatcher n’est pas thread-safe, nous devons être prudents avec l’utilisation de cette variante. Cela pourrait être probablement dangereux dans les scénarios multi-threads.

Pour résumer, dans chaque situation où nous sommes sûrs qu'il n'y a qu'un seul utilisateur desMatcher à un moment donné, vous pouvez le réutiliser avecreset. Pour le reste, il suffit de réutiliser le précompilé.

3.2. Travailler avec l'alternance

Comme nous venons de le vérifier dans la dernière section, l'utilisation inadéquate des alternances pourrait nuire aux performances. Il est important de placer les options les plus susceptibles de se produire à l'avant afin de pouvoir les associer plus rapidement.

En outre, nous devons extraire des modèles communs entre eux. Ce n’est pas la même chose de mettre:

(travel | trade | trace)

Que:

tra(vel | de | ce)

Ce dernier est plus rapide car leNFA essaiera de faire correspondre "tra" et n'essaiera aucune des alternatives s'il ne le trouve pas.

3.3. Capturer des groupes

Chaque fois que nous capturons des groupes, nous encourons une petite pénalité.

Si nous n'avons pas besoin de capturer le texte à l'intérieur d'un groupe, nous devrions envisager l'utilisation de groupes sans capture. Au lieu d'utiliser «(M)», veuillez utiliser «(?:M)».

4. Conclusion

Dans cet article rapide, nous avons brièvement revisité le fonctionnement deNFA. Nous avons ensuite exploré comment optimiser les performances de nos expressions régulières en pré-compilant nos modèles et en réutilisant unMatcher.

Enfin, nous avons souligné quelques considérations à garder à l'esprit lorsque nous travaillons avec des alternances et des groupes.

Comme d'habitude, le code source complet peut être trouvéover on GitHub.