Cours BUT Info

Ch. 9 — Entrées / sorties

Les flux de java.io : lire la nomenclature Reader/Writer et InputStream/OutputStream, tamponner, écrire avec PrintWriter, sérialiser des objets et analyser avec Scanner.

Fichiers, flux et filtres

Deux familles de classes se partagent le travail d'E/S : File décrit le fichier système (création, suppression, exploration d'arborescence), tandis que les flux (streams) transportent les données — toujours de façon séquentielle — depuis une source ou vers une destination : fichiers, sockets réseau, autres programmes. Trois flux prédéfinis existent : System.in, System.out, System.err.

Les 29 classes de java.io se déchiffrent par leur nom : un préfixe et un suffixe. Le suffixe donne le sens et la nature du flux — quatre racines abstraites, donc quatre hiérarchies de classes :

OctetsCaractères
Flux d'entréeInputStreamReader
Flux de sortieOutputStreamWriter

Le préfixe distingue les flux — la source ou la destination : File, String, ByteArray, CharArray, Pipe, Object — des filtres, qui enrobent un flux pour le traiter : Buffered (tampon), Data (types primitifs), Print (écriture formatée), LineNumber, PushBack, Sequence… Ainsi BufferedReader se lit « filtre tampon sur un flux de caractères en entrée », ObjectOutputStream « écriture d'objets sous forme de flux d'octets ».

Lire et écrire des caractères

Reader expose read() — le caractère lu, ou −1 en fin de flux —, skip, ready, close. Le premier réflexe du cours :

try (Reader fr = new FileReader(myPath + sourceFile)) {
    int c = fr.read();
    while (c != -1) {
        sb.append((char) c);
        c = fr.read();
    }
} catch (IOException e) { /* ... */ }

L'inconvénient saute aux yeux : autant d'appels à read() que de caractères dans le fichier. La solution est d'emballer le flux dans un filtre tampon : new BufferedReader(new FileReader(...)) expose readLine(), qui renvoie la ligne lue ou null en fin de fichier — un appel par ligne au lieu d'un par caractère. Seuls le constructeur et la méthode de lecture changent.

En sortie, Writer offre write (tableau, chaîne, entier), append, flush (vider les tampons vers le support) et close. Même progression : FileWriter seul, puis BufferedWriter avec sa newLine(), puis PrintWriter qui retrouve les habitudes du C — print, println, printf — et accepte directement un File à son constructeur.

Le try-with-resources

La variable déclarée entre parenthèses du try est fermée automatiquement en fin de bloc, même en cas d'exception. C'est le style de tous les exemples du cours : plus de close() oublié, plus de ressource qui fuit.

Deux styles d'écriture coexistent : des déclarations séparées (File, puis FileWriter, puis BufferedWriter) quand il faut manipuler les flux intermédiaires — fusionner plusieurs sources, réutiliser un flux —, ou tout emboîté quand on ne touche qu'au flux final.

Les flux d'octets

InputStream/OutputStream suivent le même schéma que Reader/Writer : read/ write (avec −1 en fin de flux), skip, close, flush, plus available. Leur force est la polyvalence : tout type de données — images, sons, binaires — là où les flux de caractères supposent du texte. À privilégier dès qu'une lecture « à l'œil » (human-readable) n'est pas nécessaire ; et à tamponner pour les performances, le tampon traitant les données par lots au lieu d'octet par octet.

La sérialisation

ObjectOutputStream.writeObject() et ObjectInputStream.readObject() sauvegardent et rechargent un graphe d'objets : persistance en fichier, échange entre JVM, copie profonde. Les classes liées au système (Thread, Socket, les flux eux-mêmes) échappent au mécanisme. Les règles du chapitre :

  • la classe doit implémenter java.io.Serializable — interface marqueur, sans méthode, héritée automatiquement par toutes les sous-classes ;
  • serialVersionUID est calculé par le compilateur s'il est absent : modifier la classe change la version calculée, et les données sérialisées avant la modification lèvent une InvalidClassException ;
  • transient exclut un champ de la sérialisation — obligatoire pour un champ non sérialisable (sinon NotSerializableException), prudent pour une donnée sensible : le flux expose la valeur même d'un champ private, et le champ devient null à la relecture ;
  • les membres static ne sont jamais sérialisés — quelle valeur choisir au chargement, alors qu'elle a pu changer entre deux sauvegardes ?

La sérialisation est personnalisable en déclarant dans la classe private void writeObject(ObjectOutputStream) et private void readObject(ObjectInputStream) — signatures exactes exigées —, avec defaultWriteObject()/defaultReadObject() pour réutiliser le mécanisme standard. Externalizable, qui dérive de Serializable, offre un contrôle total : par défaut aucun attribut n'y est pris en compte, transient y devient inutile.

Scanner

java.util.Scanner découpe un flux — entrée standard, fichier, simple chaîne — en jetons typés : nextInt, next, hasNext, hasNextLine… Une contrainte : il faut connaître la structure du flux, sinon InputMismatchException (demander un int sur "red"). C'est l'outil idéal pour les fichiers de données à structure fixée à l'avance, comme les CSV.

A retenir

  • suffixe = sens × nature (4 racines) ; préfixe = source ou traitement (filtre) ;
  • FileReader caractère par caractère → BufferedReader.readLine() par ligne ; PrintWriter pour formater la sortie ;
  • tamponner les flux, les fermer : try-with-resources ;
  • sérialisation : Serializable, serialVersionUID, transient, static exclu ;
  • Scanner pour les structures connues, InputMismatchException sinon.


coursOO_09.pdf

R2.01 · Cours 9 — Gestion des entrées / sorties · 37 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

Dernière mise à jour le

Sur cette page