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 :
| Octets | Caractères | |
|---|---|---|
| Flux d'entrée | InputStream | Reader |
| Flux de sortie | OutputStream | Writer |
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 ; serialVersionUIDest 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 uneInvalidClassException;transientexclut un champ de la sérialisation — obligatoire pour un champ non sérialisable (sinonNotSerializableException), prudent pour une donnée sensible : le flux expose la valeur même d'un champprivate, et le champ devientnullà la relecture ;- les membres
staticne 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) ;
FileReadercaractère par caractère →BufferedReader.readLine()par ligne ;PrintWriterpour formater la sortie ;- tamponner les flux, les fermer : try-with-resources ;
- sérialisation :
Serializable,serialVersionUID,transient,staticexclu ; Scannerpour les structures connues,InputMismatchExceptionsinon.
Chaque read() sur un FileReader coûte un accès au support. Le tampon lit par
lots dans une zone mémoire rapide et sert les lectures depuis cette zone : le
nombre d'opérations réelles chute. Bonus : readLine() donne la ligne entière.
Si la classe ne le déclare pas, le compilateur le calcule à partir de sa structure. Modifier la classe (ajout ou retrait d'un champ) produit une autre valeur : au chargement, les données sérialisées avec l'ancienne version ne correspondent plus et la relecture échoue.
Parce qu'ils appartiennent à la classe, pas à l'objet : trois instances sérialisées à des instants différents n'ont pas la même valeur du champ statique — aucune ne ferait autorité au chargement. Seul l'état de l'instance voyage.
Texte à lire ou écrire (avec un encodage de caractères) : Reader/Writer. Tout le
reste — binaires, objets sérialisés, formats propriétaires : InputStream/
OutputStream, polyvalents et à tamponner pour les performances.
coursOO_09.pdf
R2.01 · Cours 9 — Gestion des entrées / sorties · 37 diapositives
Chargement du document…
Dernière mise à jour le
Ch. 7-8 — Interfaces et collections
Le contrat d'interface et l'implémentation multiple contre le problème du diamant, List/Set/Map et leurs complexités, le contrat equals/hashCode, les parcours avec Iterator, Comparable/Comparator et final sous toutes ses formes.
Ch. 10 — Programmation générique
Types paramétrés : conventions E/K/V/T, effacement de type et ses interdictions, méthodes génériques et inférence, invariance des collections et jokers ? extends / ? super.