Notes S10

Synthèse du cours : Entrées/sorties (CS-108)

1. Concepts fondamentaux

Les entrées/sorties (E/S) désignent les échanges de données entre un programme et l’extérieur : utilisateur (console), fichiers ou réseau. Un fichier est une séquence d’octets stockée sur un support physique, accompagnée de méta-données (nom, droits, date). Un système de fichiers organise ces fichiers dans une arborescence de dossiers, où chaque élément est désigné par un chemin d’accès (séparateur / sous Unix, \ sous Windows).

On distingue deux types de fichiers : textuels (séquence de caractères) et binaires (tous les autres), même si techniquement tout fichier est une suite d’octets.

2. Les flots en Java

La bibliothèque Java propose deux paquetages : java.io (basé sur les flots/streams) et java.nio (basé sur les mémoires tampons/buffers, plus performant). Le cours se concentre sur java.io.

Un flot est une séquence de valeurs accédées séquentiellement. Quatre hiérarchies existent selon le sens et le type de données :

EntréeSortie
OctetsInputStreamOutputStream
CaractèresReaderWriter

3. Flots d’octets

InputStream — méthodes principales

  • read() : lit un octet (0-255, ou -1 en fin de flot)
  • readNBytes(...), readAllBytes() : lecture par blocs
  • transferTo(OutputStream) : transfert vers un flot de sortie
  • skip(), available() : ignorer / estimer les octets disponibles
  • mark() / reset() : marquer une position et y revenir
  • close() : fermeture indispensable pour libérer les ressources système

⚠️ Piège important : read() retourne un int entre 0 et 255, mais lorsqu’on lit dans un byte[], les valeurs vont de -128 à +127 (le -1 n’y signifie plus la fin du flot !).

Sous-classes concrètes

  • Primaires : FileInputStream (fichier), ByteArrayInputStream (tableau)
  • Filtrantes : BufferedInputStream (mémoire tampon, améliore les performances), GZIPInputStream (décompression à la volée)

Les flots peuvent être empilés. Exemple typique pour lire un fichier compressé :

new GZIPInputStream(
    new BufferedInputStream(
        new FileInputStream("in.gz")));

OutputStream

Symétrique d’InputStream avec write() (3 variantes) et flush() (force l’écriture effective). PrintStream ajoute print, println, printf — c’est le type de System.out et System.err.

4. Gestion des ressources : try-with-resources

Les flots étant liés à des ressources système, leur fermeture est cruciale. Le try-with-resources garantit l’appel automatique de close() :

java

try (InputStream s = new FileInputStream(...)) { // utilisation } // close() appelé automatiquement

Cela fonctionne pour toute classe implémentant l’interface AutoCloseable.

5. Représentation des caractères

Évolution historique

  • ASCII (7 bits, 128 caractères) : adapté à l’anglais uniquement
  • Extensions 8 bits (ISO 8859-1, Windows 1252…) : régionales
  • Unicode : standard universel visant à représenter tous les caractères

Les encodages Unicode

  • UTF-8 : taille variable (1 à 4 octets), compatible ASCII, le plus populaire
  • UTF-16 : 2 ou 4 octets
  • UTF-32 : taille fixe (4 octets)

Pour la chaîne « œuf à €1 » : 12 octets en UTF-8, 16 en UTF-16, 32 en UTF-32.

En Java

Le type char fait 16 bits (héritage historique d’Unicode). Une String est en réalité une séquence d’unités UTF-16. Conséquence : les caractères au-delà de FFFF₁₆ (comme les émojis 👍) occupent deux « caractères » Java. Ainsi :

"\uD83D\uDC4D".length()        // 2
"\uD83D\uDC4D".codePointCount(0, 2) // 1

6. Entrées/sorties textuelles

Un fichier textuel est une séquence de caractères encodés. ⚠️ L’encodage n’est généralement pas stocké dans le fichier — il faut le connaître ou le deviner. La séquence 62 c5 93 75 66 peut donner « bœuf » (UTF-8) ou « bÅ uf » (ISO 8859-1) selon l’interprétation.

Trois représentations de fin de ligne coexistent : CR (ancien macOS), LF (Unix/Linux/macOS moderne), CRLF (Windows).

Reader et Writer

Équivalents caractères de InputStream/OutputStream. Différence : Reader n’a pas available() mais ready() (car décoder des octets coûte cher).

Classes principales

  • FileReader / FileWriter : fichiers (avec encodage en argument)
  • InputStreamReader / OutputStreamWriter : passerelles entre flots d’octets et flots de caractères
  • BufferedReader ajoute readLine(), BufferedWriter ajoute newLine()
  • LineNumberReader : suit le numéro de ligne courant

Exemple de conversion d’encodage

try (Reader i = new FileReader("utf8.txt", StandardCharsets.UTF_8);
     Writer o = new FileWriter("utf16.txt", StandardCharsets.UTF_16)) {
    i.transferTo(o);
}

Points clés à retenir

  1. Toujours fermer les flots — utiliser try-with-resources
  2. Empilage de flots : la classe Buffered... améliore presque toujours les performances
  3. Octets vs caractères : deux hiérarchies parallèles (InputStream/Reader, OutputStream/Writer)
  4. Encodage : toujours le spécifier explicitement pour les fichiers textuels (UTF-8 par défaut recommandé)
  5. Pièges Unicode en Java : length() ne donne pas le nombre de caractères Unicode pour les chaînes contenant des émojis ou caractères hors BMP