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 modes — glouton, 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 :
| Quantificateur | Répète l'élément précédent | Exemple | Correspond à |
|---|---|---|---|
* | zéro ou plusieurs fois | ab*c | ac, abc, abbbc |
+ | une ou plusieurs fois | ab+c | abc, abbc (pas ac) |
? | zéro ou une fois | colou?r | color, 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..5Le 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(); // falsePourquoi 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.
| Mode | Syntaxe | Stratégie | Retour arrière ? |
|---|---|---|---|
| Glouton | X* | prendre le maximum, puis reculer si nécessaire | oui |
| Réticent | X*? | prendre le minimum, puis ajouter si nécessaire | oui |
| Possessif | X*+ | prendre le maximum et le conserver | non |
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.
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é123et12345entiers, et sur1234567n'a correspondu qu'à12345(longueur 5) — la borne supérieure5l'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, eta++ba retournéfalseinstantanément sur une longue suite deasansb, 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.