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:

 
Sélectionnez

1- BufferedReader br;
2- String line;
3- br = new BufferedReader(new FileReader(file));
4- while((line=br.readLine()) != null){
5- 		line.split("//s+");			
6- }

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 :

 
Sélectionnez

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:

 
Sélectionnez

private static final Pattern SEPARATOR_PATTERN = Pattern.compile("//s+");
		
//le reste ne change pas
5- SEPARATOR_PATTERN.split(line);
		

Cette première modification améliore le temps d'exécution qui passe à 1849 ms.

VI. 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()

 
Sélectionnez

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

Liens

article plus détaillé sur l'optimisation des regexjavaworld
le package java.util.regex du jdkjdk