Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Utilisation de la classe java.util.Scanner

Extraction de données par expressions régulières Guide de référence sur les expressions régulières



Accès rapide :
La vidéo
La classe java.util.Scanner
Passer vos propres expressions régulières au Scanner
Changer le format du séparateur de votre Scanner
Travaux pratiques
Le sujet
La correction

La vidéo

La classe utilitaire java.util.Scanner permet de parcourir un flux et d'en extraire son contenu en utilisant des expressions régulières. Cette vidéo vous montre comment utiliser cette classe.


Utilisation de la classe java.util.Scanner

La classe java.util.Scanner

Depuis la mise à disposition du Java SE 5.0, vous avez la possibilité d'utiliser une classe utilitaire très intéressante : la classe java.util.Scanner. Celle-ci est, en quelque sorte, un outil permettant de simplifier l'utilisation d'expressions régulières dans vos programmes. Cet outil peut, de plus, fonctionner de différentes manières.

On crée un scanner par-dessus un flux (System.in, par exemple). Ensuite, ce scanner permet d'extraire des informations en spécifiant une ou plusieurs expressions régulières correspondantes aux données souhaitées. Pour les principaux types de données Java, des expressions régulières adaptées sont déjà prédéfinies : vous n'aurez donc pas à les fournir. Il est aussi possible de spécifier le format des séparateurs entre chaque donnée à extraire par le biais d'une expression régulières (par défaut, un séparateur est constitué d'un ou plusieurs caractères parmi l'espace et les caractères de tabulation et de retour à la ligne).

La manière la plus simple d'utiliser cette classe, consiste à utiliser les expressions régulières qu'elle contient pour extraire des données correspondantes aux types de bases du langage Java. Pour ce faire, il faudra invoquer une méthode préfixée par next, en fonction du type de données considéré. Par exemple, si vous souhaitez extraire un entier, il faudra invoquer la méthode nextInt. Voici un premier exemple d'utilisation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
import java.util.Scanner;

public class TestScanner {

    public static void main(String[] args) {

        try ( Scanner scanner = new Scanner( System.in ) ) {
            
            System.out.print( "Veuillez saisir un premier entier : " );
            int a = scanner.nextInt();
            
            System.out.print( "Veuillez saisir un second entier : " );
            int b = scanner.nextInt();
            
            int result = a + b;
            System.out.printf( "La somme de %d et de %d vaut %d\n", a, b, result );
            
        }
        
    }
    
}
Un premier exemple d'utilisation de la classe Scanner
comme vous pouvez le constater, l'instance de Scanner utilisée par ce programme est placée dans une instruction try. Il s'agit plus précisément d'un « try-with-resources » introduit dans le langage Java SE 7.0. Cette forme de try permet de garantir que le scanner sera bien libéré en sortie de try en y invoquant automatiquement la méthode Scanner.close(). Nous reviendrons plus en détail sur cette instruction « try-with-resources » dans un futur chapitre de ce cours.

Outre la méthode nextInt, vous y retrouvez aussi nextByte, nextShort, nextLong, nextFloat, nextDouble, ... Pour chacune de ces méthodes, une expression régulière est utilisée par votre scanner. Mémorisez que pour les méthodes d'extraction d'entiers, vous pouvez aussi contrôler la base décimale à utiliser.

La méthode nextLine permet de lire une ligne de texte jusqu'au prochain retour à la ligne. Voici un exemple d'utilisation de cette méthode.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
import java.util.Scanner;

public class TestScanner {

    public static void main(String[] args) {

        try ( Scanner scanner = new Scanner( System.in ) ) {
            
            while( true ) {
                System.out.print( "Enter your login: " );
                String login = scanner.nextLine();
                
                System.out.print( "Enter your password: " );
                String password = scanner.nextLine();
                
                if ( login.equals( "Bond" ) && password.equals( "007" ) ) {
                    break;
                }
            }
            
            System.out.println( "Welcome James" );
            
            System.out.print( "How many spies are you killed: " );
            int count = scanner.nextInt();
            if ( count > 100 ) System.out.println( "You are a great spy!!!" );
        }
        
    }
    
}
Exemple d'utilisation de la méthode nextLine

Il est possible de demander au scanner si une donnée particulière est disponible. Plusieurs méthodes existent pour répondre à ce besoin, en fonction du type de la donnée souhaité. Citons notamment Scanner.hasNext() pour savoir si des caractères sont disponibles, Scanner.hasNextInt pour savoir si une donnée de type int est immédiatement disponible, ... Bien entendu, toutes ces méthodes renvoient un résultat de type booléen. Voici un petit exemple d'utilisation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
import java.util.Scanner;

public class TestScanner {

    public static void main( String[] args ) throws Exception {

        ProcessBuilder processBuilder = new ProcessBuilder( "ifconfig" );
        processBuilder.redirectErrorStream( true );
        try (Scanner scanner = new Scanner( processBuilder.start().getInputStream() )) {
            while ( scanner.hasNext() ) {
                String line = scanner.nextLine();
                if ( line.length() > 0 && line.charAt( 0 ) != ' ' ) {
                    System.out.println( line );
                }
            }
            System.out.println( "-------------------------------------\nBye bye" );
        }

    }

}
Exemple d'utilisation de la méthode Scanner.hasNext
cet exemple utilise la classe java.lang.ProcessBuilder qui permet de lancer un nouveau processus (un nouveau programme, ici la commande ifconfig sur un système linux) et de récupérer la sortie standard produite par ce processus. C'est sur cette sortie standard que nous greffons notre scanner afin d'en récupérer les lignes de texte.
le throws exception, présent dans la définition du main, est utilisé pour indiquer que nous ne souhaitons pas gérer les erreurs et que si une erreur doit survenir alors elle sera affichée dans la console et que le programme devra s'arrêter.

Vous l'avez peut-être déjà constaté, il existe une autre méthode appelée simplement next. Elle permet d'extraire une chaîne de caractères jusqu'au prochain séparateur (délimiteur). Le type de retour est, bien entendu, String. Par défaut, un séparateur est constitué d'un ou plusieurs caractères parmi l'espace, un caractère de tabulation ou un caractère de retour à la ligne.

Passer vos propres expressions régulières au Scanner

Revenons à nos expressions régulières. Pour indiquer à notre scanner le format de la prochaine donnée à extraire, nous utilisons la méthode Scanner.next( regExp ). Attention, si cette donnée ne correspond pas au format de votre expression régulière, une erreur (une exception) de type java.util.InputMismatchException sera déclenchée.

Le programme suivant demande à saisir trois informations, chacune d'un type différent, et récupère ces données grâce au un scanner. Chaque donnée doit être séparée de la précédente via un (ou plusieurs) espace(s) ou retour(s) à la ligne.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
import java.util.InputMismatchException;
import java.util.Locale;
import java.util.Scanner;

public class TestScanner {

    public static void main( String[] args ) throws InputMismatchException {

        try ( Scanner scanner = new Scanner( System.in ) ) {
            // La valeur double devra utiliser le . comme séparateur de partie décimale
            scanner.useLocale( Locale.ENGLISH );
            
            System.out.print( "Enter an IP address, a double value and an email: " );
            
            String ip = scanner.next( "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" );
            System.out.println( "IP address == " + ip );
            
            double value = scanner.nextDouble();
            System.out.println( "double == " + value );

            String email = scanner.next( "[\\w.-]+@[\\w.-]+\\.[a-z]{2,}" );
            System.out.println( "email == " + email );
        }
        
    }

}
Exemple d'utilisation de la méthode Scanner.next

Testons ce programme : je vais le lancer une fois en y saisissant des données compatible avec les types attendus, puis une seconde fois avec des données non compatibles, histoire de voir les résultats produits. Voici ces résultats.

$> java TestScanner
Enter an IP address, a double value and an email: 127.0.0.1    3.1415    user@enterprise.com
IP address == 127.0.0.1
double == 3.1415
email == user@enterprise.com
$> java TestScanner
Enter an IP address, a double value and an email: 127.0.0.1    user@enterprise.com
IP address == 127.0.0.1
Exception in thread "main" java.util.InputMismatchException
    at java.util.Scanner.throwFor(Scanner.java:864)
    at java.util.Scanner.next(Scanner.java:1485)
    at java.util.Scanner.nextDouble(Scanner.java:2413)
    at TestScanner.main(TestScanner.java:18)
$> 

Changer le format du séparateur de votre Scanner

Vous pouvez aussi utiliser une expression régulière pour définir le format des séparateurs utilisés par votre scanner. A titre d'exemple, nous allons réutiliser le programme précédent, mais nous allons y changer le séparateur : il sera maintenant constitué du caractère ; préfixé et suffixé d'au moins un blanc (dans notre exemple la tabulation ne sera plus supportée). L'expression régulière sera la suivante : +; +. Voici la nouvelle version du programme.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
import java.util.InputMismatchException;
import java.util.Locale;
import java.util.Scanner;

public class TestScanner {

    public static void main( String[] args ) throws InputMismatchException {

        try ( Scanner scanner = new Scanner( System.in ) ) {
            // La valeur double devra utiliser le . comme séparateur de partie décimale
            scanner.useLocale( Locale.ENGLISH );
            scanner.useDelimiter( " +; +" );
            
            System.out.print( "Enter an IP address, a double value and an email: " );
            
            String ip = scanner.next( "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" );
            System.out.println( "IP address == " + ip );
            
            double value = scanner.nextDouble();
            System.out.println( "double == " + value );

            String email = scanner.next( "[\\w.-]+@[\\w.-]+\\.[a-z]{2,}" );
            System.out.println( "email == " + email );
        }
        
    }

}
Exemple d'utilisation de la méthode Scanner.next

Testons maintenant notre nouveau séparateur.

$> java TestScanner
Enter an IP address, a double value and an email: 127.0.0.1 ; 3.1415   ;   user@enterprise.com ; 
IP address == 127.0.0.1
double == 3.1415
email == user@enterprise.com
$>
il ne faut pas oublier le dernier séparateur dans la saisie, sans quoi le programme attend de lire ce séparateur et bloc le processus. Il y a un blanc après le dernier caractère ; !!!

Travaux pratiques

Le sujet

Commencez par créer un fichier appelé data.csv. Le format CSV (Comma Separated Value) est souvent utilisé par les tableurs des packs office (Libre Office Calc, ...). Il permet d'organiser des données sous forme tabulaire. Deux principaux séparateurs sont couramment utilisés : la virgule (comma en anglais) ou le point-virgule (semicolon en anglais). Traditionnellement, la première ligne du fichier définie les titres des colonnes du fichier. Voici le contenu de notre fichier data.csv.

Identifier;First name;Last name;Email
0;Johnny;English;bean@mi0.uk
7;James;Bond;007@mi6.uk
8;Jason;Bourne;bourne@cia.us
9;Hubert;Bonisseur De La Bath;oss117@espions.fr

Bien entendu, nous n'avons pas encore abordé la gestion des fichiers dans ce cours. Je vous propose donc un code de base réalisant l'ouverture du fichier. Il faut juste savoir qu'un fichier en lecture, et en Java, sera géré par la classe java.io.FileInputStream. Cette classe est compatible, par héritage, avec la classe java.io.InputStream qui est attendue par un scanner. Donc c'est parfait, nous devrions pouvoir extraire les données du fichier via un scanner : vous allez donc pouvoir travailler. ;-)

nous reviendrons en détail sur les concepts de fichiers et d'héritage dans les prochains chapitres de ce cours. Patience.

Voici le squelette du programme que je vous propose de développer.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
import java.io.FileInputStream;
import java.util.Scanner;

public class CsvReader {

    public static final int COLUMN_COUNT = 4;

    public static void main( String[] args ) throws Exception {

        try ( FileInputStream inputStream = new FileInputStream( "data.csv" ) ;
              Scanner scanner = new Scanner( inputStream ) ) {
            
            // Affichage 
            
        }
        
    }

}
Point de départ du programme à développer
le fichier étant ouvert dans un bloc « try-with-resources », il sera automatiquement fermé et libéré en fin de programme.

Le but du TP est simple. On sait que le tableau possède quatre colonnes : afficher le contenu du tableau sous forme tabulaire dans la console, avec la ligne de titre mise en évidence.

La correction

J'ai cherché à faire une présentation un peu sexy en présentant un tableau sur la console. Histoire que les cellules aient toutes les mêmes tailles dans le tableau, je me suis aidé de la méthode String.printf afin de pouvoir utiliser des affichages formatés.

La partie la plus délicate est certainement de correctement gérer les séparateurs. A ce niveau, je me suis un peu simplifié la vie en acceptant soit un caractère ,, soit un caractère ; ou enfin un retour à la ligne. Cette dernière possibilité est très importante si on veut correctement passer à la ligne suivante.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
import java.io.FileInputStream;
import java.util.Scanner;

public class CsvReader {

    public static final int COLUMN_COUNT = 4;

    public static void main( String[] args ) throws Exception {

        try ( FileInputStream inputStream = new FileInputStream( "data.csv" ) ;
              Scanner scanner = new Scanner( inputStream ) ) {
            
            scanner.useDelimiter( "[,;\\n]" );
            
            String identifier = scanner.next();
            String firstName = scanner.next();
            String lastName = scanner.next();
            String email = scanner.next();
            
            String segment = "--------------------";
            System.out.printf( "+%s+%s+%s+%s+\n",
                    segment, segment, segment, segment );
            System.out.printf( "|%-20s|%-20s|%-20s|%-20s|\n",
                    identifier, firstName, lastName, email );
            System.out.printf( "+%s+%s+%s+%s+\n",
                    segment, segment, segment, segment );

            while( scanner.hasNext() ) {
                identifier = scanner.next();
                firstName = scanner.next();
                lastName = scanner.next();
                email = scanner.next();
                
                System.out.printf( "|%-20s|%-20s|%-20s|%-20s|\n",
                        identifier, firstName, lastName, email );
            }
            
            System.out.printf( "+%s+%s+%s+%s+\n",
                    segment, segment, segment, segment );

        }
        
    }

}
Exemple de lecture d'un fichier CSV via un scanner

Et voici les résultats produits par cet exemple.

+--------------------+--------------------+--------------------+--------------------+
|Identifier          |First name          |Last name           |Email               |
+--------------------+--------------------+--------------------+--------------------+
|0                   |Johnny              |English             |bean@mi0.uk         |
|7                   |James               |Bond                |007@mi6.uk          |
|8                   |Jason               |Bourne              |bourne@cia.us       |
|9                   |Hubert              |Bonisseur De La Bath|oss117@espions.fr   |
+--------------------+--------------------+--------------------+--------------------+


Extraction de données par expressions régulières Guide de référence sur les expressions régulières