4. Flux d'entrée

4.1. Introduction

À chaque type de flux de sortie est associé un flux d'entrée, qui permet de lire les données qui ont été écrites, dans les mêmes conditions. Aux classes Writer et OutputStream, que nous venons de voir dans la section précédente, sont donc associées les classes Reader et InputStream respectivement.

4.2. Lecture de caractères, classe Reader

La classe Reader est une classe abstraite, étendue par CharArrayReader, StringReader et FileReader, indirectement. Ces trois classes permettent de lire des flux de caractères en provenance de tableaux de char, de chaînes de caractères, ou de fichiers. La classe Reader expose plusieurs versions d'une méthode read(), dont le fonctionnement est constant. Cette méthode permet de lire des caractères, un par un, ou en les stockant dans un tableau. Toutes ces versions de read() retournent un int. Ce nombre correspond au nombre de caractères effectivement lus sur le flux. Si ce nombre est égal à -1, c'est que la fin du flux a été rencontrée. Voyons ceci sur un exemple. Notons qu'un appel à read() peut bloquer , c'est-à-dire, ne retourner de valeur qu'au bout d'un certain temps, durant lequel le flux est en attente de l'arrivée de nouveaux caractères. Notons enfin qu'il peut arriver que le nombre de caractères lus soit plus petit que la taille du buffer, sans pour autant que la fin du flux soit atteinte.

Exemple 121. Utilisation de FileReader

public  static  void main(String[] args) {
   
    // déclaration de notre FileReader à l'exétieur des
    // blocs try {} catch {}
   FileReader fr = null ;

    try {
      
       // ouverture du flux de lecture
       // peut jeter une FileNotFoundException
      fr =  new FileReader("tmp/bonjour.text") ;

       // définition du buffer : un tableau de char
       int bufferSize =  1024 ;
       char [] buffer =  new  char[bufferSize] ;
	
       // définitions de variables pour suivre notre
       // lecture
       int n =  0 ;
       int total =  0 ;
       int loops =  0 ;

       do {
          // remplissage du buffer
          // n contient le nombre de caractères effectivement lus
         n = fr.read(buffer) ;
         total += n ;
         loops++ ;
         
       // si le nombre lu est -1, 
       // c'est que l'on a atteint la fin du flux
      }  while (n != -1) ; 

       // quelques informations sur la lecture
      System.out.println(
             "Nombre de caractères lus au total = " + total + 
             " en " + loops +  " boucles.") ;

   }  catch (FileNotFoundException e) { 

       // gestion de l'erreur
   
   }  catch (IOException e) {

       // gestion de l'erreur
   
   }  finally {
      
       // pattern de fermeture d'un flux
       if (fr != null) {

          try {

            fr.close() ;
            
         }  catch (IOException e) {
            
             // gestion de l'erreur
         }
      }
   }
}

La classe FileReader expose également deux méthodes utilitaires.
  • skip(long n) : permet de sauter la lecture du nombre de caractères passés en paramètre. Cette méthode retourne le nombre de caractères effectivement sautés.
  • ready() : retourne true si le flux possède des caractères prêts à être lus. Un appel à read() après un appel à ready() qui a retourné true ne bloquera donc pas.

4.3. Bufferisation, lecture ligne par ligne

Tout comme pour l'écriture, la lecture d'un flux peut être bufferisée. Le même mécanisme que pour les flux de lecture est utilisé, il consiste à construire un flux en lui passant en paramètre un autre flux. Le fonctionnement de ce flux peut alors changé (dans notre cas, il sera bufferisé), et de nouvelles méthodes d'accès peuvent être utilisées. La classe permettant de bufferiser un flux d'entrée de caractères est BufferedReader. En plus de sa fonctionnalité de bufferisation, elle expose une méthode très utile :
  • readLine(), qui retourne une chaîne de caractères ( String) contenant la ligne lue.
Elle est étendue par la classe LineNumberReader(), qui permet de récupérer le numéro de ligne. Voyons ceci sur un exemple.

Exemple 122. Lecture d'un fichier ligne par ligne : LineNumberReader

// le début du code est inchangé

   fr =  new FileReader("tmp/bonjour.text") ;
    // ouverture d'un flux sur le flux de lecture du fichier
   LineNumberReader lnr =  new LineNumberReader(fr) ;
   
   String line = null ;

    do {
      
       // lecture du numéro de la ligne courante
       int number = lnr.getLineNumber() ;
       // lecture d'une ligne
      line = lnr.readLine() ;
      
       // affichage de cette ligne, et de son numéro
       if (line != null) {
         System.out.println("[" + number +  "] : " + line) ;
      }

   }  while (line != null) ;

 // la fin du code est inchangée 

4.4. Lecture d'octets : InputStream

Le pendant de la classe OutputStream est la classe InputStream. Cette classe abstraite est la base des classes de lecture des flux d'octets. Elle est étendue par
  • FileInputStream pour la lecture dans des fichiers ;
  • ByteArrayInputStream pour la lecture dans des tableaux d'octets ;
  • DataInputStream pour la lecture des types primitifs Java ;
  • ObjectInputStream pour la lecture des objets sérialisés.
Le fonctionnement de la lecture d'octets sur un flux suit le même pattern que la lecture de caractères. Les octets sont lus dans un tableau, qui sert de buffer, et la méthode read() retourne le nombre d'octets effectifs qui ont été lus. Lorsque ce nombre est -1, la fin du flux a été atteinte. La classe InputStream expose les méthodes suivantes.
  • read(), read(byte[] buf) et read(byte[] buf, int offset, int length) : ces méthodes permettent de lire les octets dans un tableau. Elles retournent toutes le nombre d'octets effectivement lus.
  • skip(long n) : saute le nombre d'octets passé en paramètre dans le flux de lecture.
  • reset() : réinitialise la lecture de ce flux.
  • available() : retourne le nombre d'octets disponibles dans un int. Ce nombre d'octets peut être lu, ou sauté. La lecture qui suit ne bloquera pas, tant que ce nombre ne sera pas dépassé.
Tout comme pour les flux de caractères, il se peut que le nombre d'octets lus soit plus petit que la taille du buffer utilisé, sans pour autant que la fin du flux soit atteinte. Voyons un exemple de lecture d'octets.

Exemple 123. Utilisation de FileInputStream

public  static  void main(String[] args) {
   
    // déclaration de notre InputStream à l'exétieur des
    // blocs try {} catch {}
   InputStream is = null ;

    try {
      
       // ouverture du flux d'entrée sur un fichier
       // peut jeter une FileNotFoundException
      is =  new FileInputStream(fichier) ;

       // définition du buffer : un tableau d'octets
       int bufferSize =  1024 ;
       byte [] buffer =  new  byte[bufferSize] ;
	
       // définitions de variables pour suivre notre
       // lecture
       int n =  0 ;
       int total =  0 ;
       int loops =  0 ;

       do {
          // remplissage du buffer
          // n contient le nombre d'octets effectivement lus
         n = is.read(buffer) ;
         total += n ;
         loops++ ;
         
       // si le nombre lu est -1, 
       // c'est que l'on a atteint la fin du flux
      }  while (n != -1) ; 

       // quelques informations sur la lecture
      System.out.println(
             "Nombre d'octets lus au total = " + total + 
             " en " + loops +  " boucles.") ;

   }  catch (FileNotFoundException e) { 

       // gestion de l'erreur
   
   }  catch (IOException e) {

       // gestion de l'erreur
   
   }  finally {
      
       // pattern de fermeture d'un flux
       if (fr != null) {

          try {

            fr.close() ;
            
         }  catch (IOException e) {
            
             // gestion de l'erreur
         }
      }
   }
}

4.5. Lecture de types primitifs : DataInputStream

La classe DataInputStream expose un jeu de méthodes qui permet de lire les types primitifs Java directement dans des flux binaires : readInt(), readLong(), etc... Cette classe expose de plus une méthode readFully(byte[] buf), implémentation de la même méthode de l'interface DataInput. Le contrat de cette méthode est différent de celui de la méthode read(byte[] buff) exposée entre autres par la classe InputStream. La différence entre les deux est subtile, et mérite d'être expliquée ici. La méthode read(byte [] buff) lit un certain nombre d'octets, et les range dans le tableau passé en paramètre. Lorsqu'elle décide de rendre la main, le nombre d'octets effectivement écrit est retourné par cette méthode. Rien n'impose que ce nombre soit égal à la taille du tableau, il arrive souvent que ce ne soit pas le cas. La méthode readFully(byte[] buff), elle, garantit que le tableau retourné est bien rempli. Notons que cette méthode ne retourne rien. Si la fin du flux est rencontrée durant la lecture, alors une exception de type EOFException est jetée, et le tableau passé en paramètre est partiellement rempli, sans que l'on puisse savoir de combien de cases. Cette méthode doit donc être utilisée avec précautions.

4.6. Lecture d'objets : ObjectInputStream

De même que pour l'écriture directe d'objets, la lecture d'objets (appelée aussi désérialisation) sera vue dans une section à part.
Java langage & API
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction : un peu d'histoire
1. Java : genèse d'un nouveau langage
Programmer en Java
1. Un premier exemple
1.1. Aperçu général, cycle de vie
1.2. Un premier programme
1.3. Programmes, applets, servlets, etc...
2. Une première classe
2.1. Écriture d'une classe
2.2. Instanciation de cette classe
3. Types de base, classes et objets
3.1. Types de base
3.2. Classes et objets
Classes Object et String
1. Introduction
2. La classe Object
2.1. La méthode toString()
2.2. La méthode clone()
2.3. La méthode equals()
2.4. La méthode hashCode()
2.5. La méthode finalize()
2.6. La méthode getClass()
3. La classe String
3.1. Introduction
3.2. Construction d'un objet de type String
3.3. Concaténation, StringBuffer et StringBuilder
3.4. Concaténations de chaînes de caractères depuis Java 8
3.5. Concaténations de chaînes de caractères depuis Java 11
3.6. Extraction d'une sous-chaîne de caractères
3.7. Comparaison de deux chaînes de caractères
3.8. Méthodes de comparaisons lexicographiques
3.9. Méthode de recherche de caractères
3.10. Méthode de modification de chaîne
3.11. Méthode de duplication
3.12. Support de l'unicode, internationalisation
Structure d'une classe
1. Introduction
2. Classes
2.1. Classes publiques
2.2. Classes internes
2.3. Classe membre
2.4. Classes locales
2.5. Classes anonymes
2.6. Le mot-clé this
3. Éléments statiques
3.1. Champ statique
3.2. Cas des constantes
3.3. Bloc statique
3.4. Classe membre statique
4. Membres d'une classe, visibilité
4.1. Bloc non statique
4.2. Accès à un membre, visibilité
4.3. Les champs
4.4. Signature d'une méthode
4.5. Les méthodes
4.6. Getters et Setters
5. Constructeur, instanciation
5.1. Chargement d'une classe
5.2. Constructeurs d'une classe
5.3. Instanciation d'un objet
5.4. Destruction d'objets
5.5. Le mot-clé final
6. Énumérations
6.1. Déclaration d'une énumération
6.2. Classe énumération
6.3. Méthode toString()
6.4. Méthode valueOf()
6.5. Méthode values()
6.6. Méthode ordinal()
6.7. Méthode compareTo()
6.8. Constructeurs privés
6.9. Classe utilitaire : EnumSet
Noms, opérateurs, tableaux
1. Introduction
2. Identificateurs, noms et expressions
2.1. Identificateurs et noms
2.2. Expressions
3. Opérateurs, ordre d'exécution
3.1. Ordre d'exécution
3.2. Les opérateurs
3.3. Les opérateurs ++ et --
3.4. Les opérateurs % et /
3.5. Les opérateurs <<, >> et >>>
3.6. L'opérateur instanceof
3.7. Les opérateurs &, | et ^
3.8. Les opérateurs && et ||
3.9. L'opérateur ? ... :
3.10. Les opérateurs d'affectation
4. Tableaux
4.1. Création d'un tableau
4.2. Initialisation d'un tableau
4.3. Utilisation d'un tableau comme un Object
4.4. Tableaux de tableaux
4.5. Copie de tableaux
5. Blocs, boucles et contrôles
5.1. Blocs
5.2. Mots-clés réservés
5.3. Tests : if et switch
5.4. Boucles : for, while, do ... while
5.5. Commandes continue et break
5.6. Commandes return et goto
Nombres, précision, calculs
1. Introduction
2. Calculs
2.1. Précision
2.2. Codage des nombres flottants
2.3. Le mot-clé strictfp
2.4. Conversion de types
3. Dépassements de capacité
3.1. Cas des entiers
3.2. Cas des flottants
3.3. Bibliothèques BigInteger et BigDecimal
4. Fonctions mathématiques
4.1. Fonctions usuelles
4.2. Générateurs aléatoires
5. Classes enveloppe
5.1. Associer les types de base à des objets
5.2. Auto-boxing
Héritage, abstraction, interfaces
1. Introduction
2. Abstraction et encapsulation
2.1. Abstraction
2.2. Encapsulation
3. Héritage
3.1. Définition de l'héritage
3.2. Conséquences pour les membres
3.3. Polymorphisme
3.4. Empêcher l'héritage
4. Classes abstraites
5. Interfaces
5.1. Introduction
5.2. Définition
5.3. Java 8 et les interfaces
5.4. Utilisation des interfaces
5.5. Définition de constantes dans les interfaces
5.6. Utilité des interfaces
Packages
1. Introduction
2. Notion de paquet
2.1. Déclaration d’appartenance à un paquet
2.2. Chargement d’une classe
2.3. Choix de nom
3. Archives, chemin de recherche, classpath
3.1. Archives
3.2. Variable CLASSPATH
3.3. Notion de classloader
3.4. Bilan sur les classes chargées
3.5. Visibilité
3.6. Conseils d'écriture, bibliothèque standard
Exceptions
1. Introduction
2. Erreurs et Exceptions
2.1. Classe Throwable, notion de stack trace
2.2. Classe Error
2.3. Classe RuntimException
2.4. Classe Exception
3. Déclenchement d'une exception
3.1. Exceptions déclenchées par la JVM
3.2. Exceptions déclenchées par l'application
4. Capter une exception
4.1. Traiter une exception localement
4.2. Code de captage
5. Créer ses propres exceptions
Entrées / sorties
1. Introduction
2. Notion de fichier
2.1. Introduction
2.2. La classe File
2.3. Construction d'une instance de File
2.4. Méthodes exposées
3. Flux de sortie
3.1. Introduction, notion de flux
3.2. Écriture de caractères, classe Writer
3.3. Bufferisation, construction d'un flux sur un autre
3.4. Utilisation de PrintWriter
3.5. Écriture d'octets, OuputStream
3.6. Écriture de types primitifs : DataOutputStream
3.7. Écriture d'objets : ObjectOutputStream
4. Flux d'entrée
4.1. Introduction
4.2. Lecture de caractères, classe Reader
4.3. Bufferisation, lecture ligne par ligne
4.4. Lecture d'octets : InputStream
4.5. Lecture de types primitifs : DataInputStream
4.6. Lecture d'objets : ObjectInputStream
5. Lecture et écriture de flux croisés
5.1. Introduction
5.2. Lire des caractères sur un flux binaire : classe InputStreamReader
5.3. Écrire des caractères sur un flux binaire : classe OutputStreamWriter
5.4. Remarque sur les jeux de caractères
6. Serialization d'objets
6.1. Enjeu de la sérialization d'objets
6.2. Serialization d'un objet
6.3. Sérialization d'une grappe d'objets
6.4. Première surcharge : méthode writeObject() readObject()
6.5. Deuxième surcharge : utilisation d'un externalizer
6.6. Troisième surcharge : utilisation d'un objet proxy
7. Flux compressés
7.1. Introduction
7.2. Flux de type gzip
7.3. Flux de type zip