Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Au coeur du JDK : la classe Scanner

Une classe injustement méconnue du JDK est Scanner (java.util.Scanner).
Elle offre pourtant des fonctionnalités très intéressantes pour parser des chaînes de caractères, et en extraire et convertir les composants.

Un Scanner peut se brancher sur à peu près n'importe quelle source : InputStream, Readable (et donc Reader), File... et bien sûr une simple String. Ensuite, deux options s'offrent à vous : utiliser les méthodes de type hasNext...() / next...(), ou alors les méthodes de type find...() / match() / group().

Dans ce billet, nous verrons comment utiliser ces deux jeux d'instructions.

Première méthode : hasNext() / next()

Voyons une première façon d'utiliser un Scanner pour lire des flux de texte.

Elle se décompose en deux étapes :

  • Premièrement, découper la chaîne de caractères en tokens grâce à un délimiteur ; il s'agit par défaut d'un caractère "blanc" (espace, tabulation, retour à la ligne...), mais il est évidemment possible de fournir sa propre expression via la méthode useDelimiter(expression).
  • Ensuite, utiliser les méthodes de type hasNext...() et next...() pour parcourir, récupérer et convertir ces tokens.

Les méthodes de type hasNext...() (hasNextInt(), hasNextFloat()...) fonctionnent sur le même principe qu'un Iterator, et indiquent si le prochain token existe et s'il est bien du type spécifié. Une fois cette vérification effectuée, les méthodes de la forme next...() (nextInt(), nextFloat()...) permettent de récupérer ledit token, directement converti dans le type adéquat.
A noter qu'existent également les méthodes simples hasNext() et next(), qui permettent de savoir s'il existe un token (de n'importe quel type) et de le récupérer sous forme de String.

A l'aide de ces méthodes, il est très facile de parser une chaîne dont vous maîtrisez parfaitement le format, par exemple un fichier .csv :

  1. String s =
  2. "Dalton;Joe;1.4\n" +
  3. "Dalton;Jack;1.6\n" +
  4. "Dalton;William;1.8\n" +
  5. "Dalton;Averell;2.0";
  6. Scanner scan = new Scanner(s);
  7. scan.useDelimiter(";|\n");
  8. scan.useLocale(Locale.US); // Pour les floats
  9.  
  10. while(scan.hasNextLine()) {
  11. System.out.printf("%2$s %1$s : %3$.1f m %n", scan.next(), scan.next(), scan.nextFloat());
  12. }
Joe Dalton : 1,4 m 
Jack Dalton : 1,6 m 
William Dalton : 1,8 m 
Averell Dalton : 2,0 m 

Si au contraire le format de la chaîne vous est inconnu, par exemple si elle est saisie par l'utilisateur, il est plus prudent de recourir au pattern suivant, pour éviter les erreurs de conversion de type (InputMismatchException) :

  1. Scanner scan = new Scanner(System.in);
  2. System.out.println("Entrez un nombre entier à incrémenter :");
  3. // Existe-t-il un token ?
  4. while(scan.hasNext()) {
  5. // Est-ce un int ? On l'incrémente
  6. if (scan.hasNextInt()) {
  7. int nb = scan.nextInt();
  8. System.out.println(nb + " +1 = " + ++nb);
  9. }
  10. // Ce n'est pas un int, affichage d'un message d'erreur.
  11. else {
  12. System.out.println(scan.next() + " n'est pas un nombre.");
  13. }
  14. }
  15. scan.close();

Notez que la classe Scanner simplifie beaucoup votre code, car vous n'avez plus à convertir manuellement les tokens ni à gérer des exceptions de bas niveau.

A titre de comparaison, voici le code effectuant le même traitement sans utiliser la classe Scanner :

  1. BufferedReader reader = null;
  2. String line = null;
  3. try {
  4. reader = new BufferedReader(new InputStreamReader(System.in));
  5. while ((line=reader.readLine()) != null) {
  6. try {
  7. System.out.println(Integer.parseInt(line)+1);
  8. } catch (NumberFormatException e) {
  9. System.out.println(line + " n'est pas un nombre.");
  10. }
  11. }
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. } finally {
  15. if (reader!=null) {
  16. try {
  17. reader.close();
  18. } catch (IOException e) {
  19. }
  20. }
  21. }

Même pour une simple lecture de fichier ligne par ligne, Scanner remplace avantageusement le traditionnel BufferedReader :

  1. public static void readFileWithBufferedReader(String fileName) {
  2. BufferedReader reader = null;
  3. try {
  4. reader = new BufferedReader(new FileReader(fileName));
  5. String line = null;
  6. while ((line=reader.readLine())!=null) {
  7. System.out.println(line);
  8. }
  9. } catch (FileNotFoundException e) {
  10. e.printStackTrace();
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. } finally {
  14. if (reader!=null) {
  15. try {
  16. reader.close();
  17. } catch (IOException e) {
  18. }
  19. }
  20. }
  21. }
  22.  
  23. public static void readFileWithScanner(String fileName) {
  24. Scanner scan = null;
  25. try {
  26. scan = new Scanner(new File(fileName));
  27. while(scan.hasNextLine()) {
  28. System.out.println(scan.nextLine());
  29. }
  30. } catch (FileNotFoundException e) {
  31. e.printStackTrace();
  32. } finally {
  33. if (scan!=null) scan.close();
  34. }
  35. }

Deuxième méthode : find() / match() / group()

La seconde utilisation possible d'un Scanner est la recherche d'éléments précis dans un texte, en s'appuyant sur des expressions régulières .

Cette fois encore, c'est très simple :

  • Premièrement, il faut définir le format attendu de la ligne, à l'aide d'une expression régulière compatible avec la classe java.util.regex.Pattern.
  • Ensuite, il n'y a plus qu'à récupérer les informations qui nous intéressent en fonction de leur index, grâce à la méthode group(index).

Par exemple, vous possédez un fichier répertoriant les plus grands délinquants du Far West, et vous souhaitez en extraire les nom, prénom et taille de ces individus.

Le bandit Joe Dalton mesure 1.40m.
Le bandit Jack Dalton mesure 1.60m.
Le bandit William Dalton mesure 1.80m.
Le bandit Averell Dalton mesure 2m.

Chaque ligne répond visiblement au pattern suivant :

Le bandit (\\w+) (\\w+) mesure (\\d+(\\.\\d+)?)m\\.

Notez que le prénom est la première expression capturée (index 1), le nom la seconde (index 2), et la taille la troisième (index 3).

Utilisons Scanner pour extraire ces informations du fichier :

  1. Pattern pattern = Pattern.compile("Le bandit (\\w+) (\\w+) mesure (\\d+(\\.\\d+)?)m\\.");
  2. Scanner scan = new Scanner(daltons); // Fichier, String...
  3. while (scan.findInLine(pattern) != null) {
  4. MatchResult match = scan.match();
  5. System.out.printf("%s, %s : %.2fm%n", match.group(2), match.group(1), Float.valueOf(match.group(3)));
  6. if (scan.hasNextLine()) scan.nextLine(); // Avance à la ligne suivante, si elle existe.
  7. }
  8. scan.close();
Dalton, Joe : 1,40m
Dalton, Jack : 1,60m
Dalton, William : 1,80m
Dalton, Averell : 2,00m

Conclusion

La classe Scanner, introduite avec Java 5.0, est injustement méconnue. Elle offre pourtant des fonctionnalités intéressantes, qui simplifient la lecture et l'interprétation de texte pouvant provenir de sources variées.

J'espère que ce billet vous aura donné envie de l'utiliser !


Commentaires

1. Le mercredi 3 mars 2010, 15:41 par trouloulou

Quelle difference avec StringTokenizer ?
La partie next() y ressemble tellement que je me demande que privilégier entre Scanner et StringTokenizer.

2. Le mercredi 3 mars 2010, 15:58 par Olivier Croisier

Avec StringTokenizer, tous les tokens doivent être délimités par le même séparateur. Avec Scanner, on donne au contraire un template de ligne, duquel on extrait les informations intéressantes.

Par exemple, dans l'exemple des Daltons, on pourrait choisir de ramener le nom et le prénom comme un seul token, malgré le fait qu'il contienne des espaces :

"Le bandit (\\w+ \\w+) mesure (\\d+(\\.\\d+)?)m\\."

De plus, Scanner nous permet de récupérer la taille du Dalton malgré le "m" accolé ; avec StringTokenizer on serait obligé de le supprimer manuellement avant de convertir la taille en float.

3. Le mercredi 3 mars 2010, 16:02 par trouloulou

Réponse : http://stackoverflow.com/questions/...

StringTokenizer est beaucoup plus rapide mais les délimiteur doivent être fixe au cours du parcours.

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.