CodeGym /Cours /JAVA 25 SELF /Lecture et écriture de fichiers binaires : InputStre...

Lecture et écriture de fichiers binaires : InputStream, OutputStream

JAVA 25 SELF
Niveau 36 , Leçon 2
Disponible

1. Introduction

Commençons par le plus simple : en quoi un fichier binaire diffère-t-il d’un fichier texte ? Un fichier texte est un fichier que l’on peut ouvrir dans un éditeur basique et y voir des lettres, des chiffres, des espaces et d’autres symboles. Par exemple, my_notes.txt ou poem.txt.

Un fichier binaire est un fichier qui ne contient pas du texte, mais des octets arbitraires. Cela peut être une image (.jpg, .png), de la musique (.mp3), une archive (.zip), un exécutable (.exe), une vidéo (.mp4), un fichier de base de données, etc. Si vous ouvrez un tel fichier dans un éditeur, vous verrez quelque chose comme ÿØÿà ou tout un mur de symboles incompréhensibles. C’est normal ! L’ordinateur « comprend » uniquement des octets : pour lui, texte, image et vidéo ne sont qu’une suite d’octets. Dans les fichiers texte, ces octets peuvent être interprétés comme des caractères, alors que dans les fichiers binaires ce sont des données « brutes », non destinées à la lecture par un humain.

Classes de base pour travailler avec des fichiers binaires

En Java, on utilise des flux d’octets pour travailler avec des fichiers binaires :

  • InputStream — classe de base pour la lecture d’octets.
  • OutputStream — classe de base pour l’écriture d’octets.

Pour les fichiers, il existe des implémentations concrètes :

  • FileInputStream — lit des octets depuis un fichier.
  • FileOutputStream — écrit des octets dans un fichier.

Si vous avez entendu parler de FileReader et FileWriter, sachez qu’ils travaillent avec des caractères et ne conviennent qu’au texte. Pour les fichiers binaires, utilisez uniquement InputStream/OutputStream et leurs sous-classes.

2. Lecture de fichiers binaires

Lecture octet par octet

La méthode la plus simple consiste à lire le fichier octet par octet. C’est parlant, mais très lent.

try (FileInputStream in = new FileInputStream("image.jpg")) {
    int b;
    while ((b = in.read()) != -1) {
        // b est un nombre de 0 à 255 (octet), -1 signifie fin de fichier
        // On peut traiter l’octet, par exemple additionner tous les octets
    }
}

La méthode read() renvoie l’octet suivant comme int (de 0 à 255), et quand le fichier est terminé — renvoie -1. En général, on lit octet par octet seulement si l’on a un besoin très spécifique (par exemple, analyser la structure d’un fichier).

Lecture par blocs (avec tampon)

Lire octet par octet, c’est comme aller au magasin pour chaque pomme séparément. Il est bien plus efficace de prendre tout un sac ! En Java, il existe pour cela la méthode read(byte[] buffer), qui remplit un tableau avec des octets lus depuis le fichier.

try (FileInputStream in = new FileInputStream("image.jpg")) {
    byte[] buffer = new byte[4096]; // tampon de 4 Ko
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        // buffer contient bytesRead octets du fichier
        // On peut traiter ces octets, par exemple les enregistrer ailleurs
    }
}

La méthode read(buffer) renvoie combien d’octets ont réellement été lus (cela peut être inférieur à la taille du tampon, surtout pour la dernière lecture). Cette approche est beaucoup plus rapide, car on effectue moins d’accès au disque.

Exemple : copie de fichier

Écrivons un petit programme qui copie n’importe quel fichier binaire (par exemple, une image) d’un endroit à un autre.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryCopyExample {
    public static void main(String[] args) {
        String source = "cat.jpg";
        String dest = "cat_copy.jpg";

        try (FileInputStream in = new FileInputStream(source);
             FileOutputStream out = new FileOutputStream(dest)) {

            byte[] buffer = new byte[8192]; // 8 Ko — une taille optimale pour la plupart des cas
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("Copie terminée !");
        } catch (IOException e) {
            System.out.println("Erreur lors de la copie : " + e.getMessage());
        }
    }
}

C’est simple : on lit des blocs depuis le fichier source et on les écrit aussitôt dans le nouveau fichier. Cette méthode fonctionne avec tous les types de fichiers : images, archives, vidéos.

3. Écriture de fichiers binaires

Écriture d’un tableau d’octets

Si vous avez un tableau d’octets (par exemple, vous l’avez reçu depuis le réseau ou généré dans le programme), vous pouvez l’écrire dans un fichier comme ceci :

byte[] data = new byte[] {1, 2, 3, 4, 5}; // exemple de tableau

try (FileOutputStream out = new FileOutputStream("data.bin")) {
    out.write(data); // écrit tout le tableau dans le fichier
}

La méthode write(byte[]) écrit tous les octets du tableau. Il est aussi possible d’écrire seulement une partie du tableau : out.write(data, offset, length).

Écriture d’un fichier par morceaux (par exemple lors d’une copie)

Comme pour la lecture, on utilise généralement un tampon :

try (FileInputStream in = new FileInputStream("source.bin");
     FileOutputStream out = new FileOutputStream("dest.bin")) {

    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

Ici, tout ce que nous lisons du fichier est immédiatement écrit dans un autre fichier. Ce code se rencontre souvent dans des programmes d’archivage, des téléchargeurs, des traitements d’images, etc.

4. Points utiles

Pourquoi ne pas utiliser Reader/Writer pour les fichiers binaires ?

Reader et Writer travaillent avec des caractères (char), pas avec des octets. Ils convertissent automatiquement des octets en caractères selon un encodage (par exemple, UTF-8). C’est pratique pour le texte, mais pour les fichiers binaires — potentiellement désastreux !

Si vous essayez d’écrire une image via FileWriter, vous obtiendrez un fichier corrompu, impossible à ouvrir. Retenez : pour tout fichier non texte, utilisez uniquement InputStream/OutputStream !

Différences et subtilités importantes pour les fichiers binaires

  • Taille du tampon : Un tampon trop petit ralentira le travail (trop d’accès disque), trop grand — occupera de la mémoire inutilement. 416 Ko — généralement optimal.
  • Gestion des erreurs : Gérez toujours IOException — le fichier peut ne pas exister, être verrouillé ou l’espace disque peut être épuisé.
  • Fermeture des flux : Utilisez try-with-resources — cela garantit la fermeture des fichiers même en cas d’erreur.
  • Écrasement du fichier : Si vous ouvrez un fichier via new FileOutputStream("file.bin"), il sera écrasé. Pour ajouter à la fin, utilisez le constructeur avec le paramètre append = true.
  • Droits d’accès : Si le programme ne peut pas ouvrir le fichier, vérifiez les droits de lecture/écriture.
  • readAllBytes() : Permet de lire tout le fichier dans un tableau d’octets en une seule instruction. Pour les gros fichiers — n’utilisez pas, afin d’éviter d’épuiser toute la mémoire !

5. Erreurs courantes lors du travail avec des fichiers binaires

Erreur n° 1 : Utilisation de FileReader/FileWriter pour des fichiers binaires. Cela entraînera une corruption des données, car ces classes convertissent des octets en caractères et inversement, ce qui est catastrophique pour les images, archives, etc.

Erreur n° 2 : Ignorer la valeur de retour de read(). La méthode read(byte[]) peut lire moins d’octets que demandé, surtout pour le dernier bloc. Utilisez toujours la valeur de retour pour savoir combien d’octets ont réellement été traités.

Erreur n° 3 : Oublier de fermer le flux. Si le fichier n’est pas fermé, il peut rester verrouillé, et les données peuvent ne pas être complètement écrites (surtout lors de l’écriture !). Utilisez try-with-resources.

Erreur n° 4 : Tenter de lire le fichier entier en mémoire sans tenir compte de sa taille. Pour les fichiers volumineux, cela conduira à un OutOfMemoryError. Utilisez un tampon et lisez par morceaux.

Erreur n° 5 : Ne pas gérer les exceptions. Le travail avec des fichiers peut toujours provoquer des erreurs : fichier introuvable, droits insuffisants, disque plein. N’oubliez pas de gérer IOException.

1
Mission
JAVA 25 SELF, niveau 36, leçon 2
Bloqué
Extraction d'un fragment précieux d'un flux binaire 🧪
Extraction d'un fragment précieux d'un flux binaire 🧪
1
Mission
JAVA 25 SELF, niveau 36, leçon 2
Bloqué
Calcul du potentiel énergétique de l'artefact ⚛️
Calcul du potentiel énergétique de l'artefact ⚛️
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION