W3docs

Quantificateurs Regex en Java

Comment les quantificateurs regex Java (*, +, ?, {n,m}) se comportent en modes glouton, réticent et possessif.

Un quantificateur indique combien de fois l'élément qui le précède peut se répéter. * signifie « zéro ou plusieurs fois », + signifie « une ou plusieurs fois », ? signifie « zéro ou une fois », et {n,m} définit une plage exacte. Ces notions sont communes à tous les moteurs regex. Ce qui perturbe les développeurs Java, c'est que chaque quantificateur existe en trois modesglouton, réticent et possessif — et c'est le mode, non le comptage, qui détermine la façon dont le moteur regex de java.util.regex parcourt l'entrée.

Cette page couvre les quatre quantificateurs de base, les trois modes de correspondance, pourquoi les motifs gloutons sur-correspondent, et comment les quantificateurs possessifs protègent contre le retour arrière catastrophique. Elle suppose que vous savez déjà compiler un Pattern et utiliser un Matcher ; si ce n'est pas le cas, commencez par Pattern et Matcher. Pour les métacaractères que vous allez répéter, consultez les classes de caractères, et pour capturer le texte répété, consultez les groupes.

Les quatre quantificateurs de base

Associez un quantificateur à un caractère unique, une classe de caractères ou un groupe, et il contrôle la répétition de ce qui se trouve immédiatement à sa gauche :

QuantificateurRépète l'élément précédentExempleCorrespond à
*zéro ou plusieurs foisab*cac, abc, abbbc
+une ou plusieurs foisab+cabc, abbc (pas ac)
?zéro ou une foiscolou?rcolor, colour
{n}exactement n fois\d{4}une année à 4 chiffres
{n,}n fois ou plus\d{2,}deux chiffres ou plus
{n,m}entre n et m fois\d{3,5}de 3 à 5 chiffres
Pattern.matches("ab*c", "abbbc");   // true  — three b's
Pattern.matches("ab+c", "ac");      // false — '+' needs at least one b
Pattern.matches("colou?r", "color");// true  — the 'u' is optional
Pattern.matches("\\d{3,5}", "1234");// true  — four digits is within 3..5

Le mode glouton par défaut

Un quantificateur nu est glouton : il consomme autant d'entrée que possible, puis revient en arrière — en rendant les caractères un à un — jusqu'à ce que le reste du motif puisse correspondre. C'est pourquoi un <.+> naïf appliqué à du HTML absorbe bien plus qu'une seule balise :

String html = "<b>one</b>";
Matcher m = Pattern.compile("<.+>").matcher(html);
m.find();
m.group(); // "<b>one</b>"  — '.+' ate everything, then backed up to the last '>'

Le moteur a d'abord pris toute la chaîne, n'a pas trouvé de > en fin, et a reculé jusqu'à ce qu'un > soit aligné — tombant sur le tout dernier.

Réticent : ajouter ? pour consommer le moins possible

Ajoutez un ? à n'importe quel quantificateur (*?, +?, ??, {n,m}?) et il devient réticent (aussi appelé paresseux) : il correspond au minimum de répétitions d'abord et s'étend seulement si nécessaire. C'est généralement ce que vous voulez lors de la recherche de jetons délimités :

String html = "<b>one</b>";
Matcher m = Pattern.compile("<.+?>").matcher(html);
m.find();
m.group(); // "<b>"  — stopped at the first '>'

Même motif, un caractère en plus, comportement opposé : le glouton <.+> renvoie toute la chaîne tandis que le réticent <.+?> renvoie uniquement la première balise.

Possessif : ajouter + et ne jamais rendre

Ajoutez un + (*+, ++, ?+, {n,m}+) et le quantificateur devient possessif : il saisit autant que possible comme un glouton, mais il refuse de revenir en arrière. Si le reste du motif échoue ensuite, toute la correspondance échoue — il n'y a pas de retour arrière pour la sauver.

// Possessive '.++' eats the final '>' too and won't return it, so no '>' is left
Pattern.compile("<.++>").matcher("<b>one</b>").find(); // false

Pourquoi sacrifier cette flexibilité ? La vitesse et la sécurité. Parce qu'un quantificateur possessif ne reconsidère jamais sa décision, il ne peut pas tomber dans le retour arrière catastrophique — l'explosion exponentielle qui bloque un thread sur des motifs comme (a+)+b alimenté d'une longue suite de a sans b. Le possessif a++b répond « pas de correspondance » presque instantanément.

ModeSyntaxeStratégieRetour arrière ?
GloutonX*prendre le maximum, puis reculer si nécessaireoui
RéticentX*?prendre le minimum, puis ajouter si nécessaireoui
PossessifX*+prendre le maximum et le conservernon

Un exemple concret : les trois modes côte à côte

Ce programme fait passer la même entrée par des quantificateurs glouton, réticent et possessif, puis illustre la plage {n,m} et un quantificateur de groupe. Tout est du pur java.util.regex du JDK.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Le glouton <.+> a affiché <b>one</b><i>two</i> — toute la chaîne. Il a tout consommé, puis est revenu en arrière jusqu'au dernier >, ce qui explique exactement pourquoi les motifs gloutons sur-correspondent à travers les délimiteurs.
  • Le réticent <.+?> a affiché <b> sur la même entrée. Le simple ? a inversé la stratégie de « maximum » à « minimum », s'arrêtant au premier > — le correctif pour l'analyse balise par balise.
  • Le possessif <.++> a affiché matches=false. Il a avalé le > final et a refusé de le rendre, donc le > de fin dans le motif n'avait rien à correspondre et toute la tentative a échoué — le prix de ne jamais revenir en arrière.
  • \d{3,5} a rejeté 12 (no match, trop peu de chiffres), accepté 123 et 12345 entiers, et sur 1234567 n'a correspondu qu'à 12345 (longueur 5) — la borne supérieure 5 l'a limité même si plus de chiffres étaient disponibles.
  • Le motif de groupe (\w+\s*){2,3} a correspondu à alpha beta gamma — trois mots, son maximum — prouvant que le quantificateur s'appliquait à tout le groupe entre parenthèses, et a++b a retourné false instantanément sur une longue suite de a sans b, montrant comment les quantificateurs possessifs évitent le retour arrière catastrophique.

Quel mode utiliser ?

  • Préférez le réticent (*?, +?) pour faire correspondre du contenu entre délimiteurs — guillemets, balises, crochets, blocs délimités. Il s'arrête au premier délimiteur fermant au lieu du dernier, ce qui correspond presque toujours à ce que vous souhaitez.
  • Restez glouton (par défaut) quand vous voulez genuinement la correspondance la plus longue possible, ou quand il n'y a de toute façon qu'une seule correspondance possible et que le ?/+ supplémentaire ne serait que du bruit.
  • Utilisez le possessif (*+, ++) comme outil de performance et de sécurité sur des entrées que vous ne contrôlez pas. Parce qu'il ne revient jamais en arrière, il ne peut pas déclencher de retour arrière catastrophique, mais il fera également échouer des correspondances qu'un quantificateur glouton aurait réussies — ne l'appliquez donc que là où vous savez que le retour arrière est inutile.

Un correctif courant en pratique : une expression régulière qui fonctionne sur de petites entrées mais se bloque sur de grandes est généralement un quantificateur glouton à l'intérieur d'un groupe, comme (\w+)+. Rendre le quantificateur interne possessif ((\w++)+ ou restructurer le motif) élimine l'explosion exponentielle.

Pratique

Pratique
Sur l'entrée '<b>one</b>', que retourne le motif Java '<.+?>' (un quantificateur réticent) au premier appel à find() ?
Sur l'entrée '<b>one</b>', que retourne le motif Java '<.+?>' (un quantificateur réticent) au premier appel à find() ?
Was this page helpful?