Introduction▲
Les expressions régulières ou regex sont de formidables outils de travail très utiles à tous les développeurs et ceci non seulement dans le monde java. Dans cet article, nous allons découvrir comment l'utilisation judicieuse des regex permet l'optimisation d'un code java.
I. Problématique▲
Nous allons illustrer la puissance des regex via un exemple très simple. Il s'agit de lire un fichier ligne par ligne et de diviser chaque ligne en un ensemble de segments séparés par un espace. Par exemple, la division de la ligne suivante donne :
Exemple
Ceci est un ligne
Résultat
Ceci,est,une,ligne
II. Première tentative▲
La premier réflexe qu'un développeur, confronté à ce problème, peut avoir est de faire appel à la méthode split de la classe String:
2.
3.
4.
5.
6.
BufferedReader br;
String line;
br =
new
BufferedReader
(
new
FileReader
(
file));
while
((
line=
br.readLine
(
)) !=
null
){
line.split
(
"//s+"
);
}
L'exécution de ce code sur un fichier texte de quelques mégas octets est de 2284 ms.
III. Première Optimisation▲
Voici le code source de la méthode split() de la classe String :
Pattern.compile
(
regex).split
(
this
,0
);
Ainsi, le pattern est compilé à chaque nouvelle ligne. Si on compile le Pattern une seule fois, nous obtiendrons sûrement de meilleures performances:
private
static
final
Pattern SEPARATOR_PATTERN =
Pattern.compile
(
"//s+"
);
// le reste ne change pas
SEPARATOR_PATTERN.split
(
line);
Cette première modification améliore le temps d'exécution qui passe à 1849 ms.
IV. Deuxième Optimisation▲
Comment améliorer encore ce code? L'analyse du code source de la méthode split() de la classe Pattern a permis d'exprimer quelques critiques à son égard:
- certains détails de la méthodes sont superflus pour ce cas et peuvent être éliminés
- une nouvelle classe Matcher est créée à chaque appel de la méthode ce qui peut être évité
Voici le nouveau code "aménagé" de la méthode split()
private
Matcher matcher =
null
;
public
String[] splitUsingMatcherOptimized
(
String str){
//éviter la création à chaque fois d'un nouveau Matcher
//via une lazy initialisation et l'utilisation de la
//méthode reset()
if
(
matcher ==
null
) {
matcher =
SEPARATOR_PATTERN.matcher
(
str);
}
else
{
matcher.reset
(
str);
}
int
index =
0
;
ArrayList<
String>
matchList =
new
ArrayList<
String>(
);
// ajoute les segments avant chaque correspondance trouvée
while
(
matcher.find
(
)) {
String match =
str.substring
(
index, matcher.start
(
));
matchList.add
(
match);
index =
matcher.end
(
);
}
// si aucune correspondance n' a été trouvée on renvoie
// le str original dans un tableau
if
(
index ==
0
)
return
new
String[]{
str}
;
// ajoute le dernier segment
String lastMatch =
str.substring
(
index, str.length
(
));
matchList.add
(
lastMatch);
// Construit le résultat
return
(
String[]) matchList.toArray
(
new
String[matchList.size
(
)]);
}
En utilisant cette nouvelle méthode, le code s'exécute maintenant en 1511 ms.
Conclusion▲
Les performances du code ont été améliorée grâce à une utilisation plus efficace des regex (un gain non négligeable de 773 ms).Nous pouvons ainsi déduire quelques règles simples concernant les regex:
- Préférer la méthode split de Pattern à celle de String
- Compiler le Pattern une seule fois lorsque la même regex sera utilisée plusieurs fois
- Réutiliser le même Matcher via la méthode reset plutôt que d'en créer un à chaque fois