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.4n«  +
  3. « Dalton;Jack;1.6n«  +
  4. « Dalton;William;1.8n«  +
  5. « Dalton;Averell;2.0 »;
  6. Scanner scan = new Scanner(s);
  7. scan.useDelimiter(« ;|n« );
  8. scan.useLocale(Locale.US); // Pour les floats
  9. while(scan.hasNextLine()) {
  10. System.out.printf(« %2$s %1$s : %3$.1f m %n », scan.next(), scan.next(), scan.nextFloat());
  11. }
 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 = &quo t; + ++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. public static void readFileWithScanner(String fileName) {
  23. Scanner scan = null;
  24. try {
  25. scan = new Scanner(new File(fileName));
  26. while(scan.hasNextLine()) {
  27. System.out.println(scan.nextLine());
  28. }
  29. } catch (FileNotFoundException e) {
  30. e.printStackTrace();
  31. } finally {
  32. if (scan!=null) scan.close();
  33. }
  34. }

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 !

Une pensée sur “Au coeur du JDK : la classe Scanner

  • 17 mars 2010 à 0 h 07 min
    Permalink

    A noter que cette classe utilisée mal à propos tueras tes performances – les regexp et de bon vieux tokeniser , ça peut suffire dans bien des cas

    Répondre

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :