CodeGym /Cours /JAVA 25 SELF /DataInputStream, DataOutputStream : manipulation des type...

DataInputStream, DataOutputStream : manipulation des types primitifs

JAVA 25 SELF
Niveau 36, Leçon 3
Disponible

1. Pourquoi utiliser DataInputStream et DataOutputStream ?

Lorsque vous travaillez avec des fichiers, il arrive qu’il faille stocker non pas seulement du texte, mais des données structurées : nombres, valeurs booléennes, tableaux de types primitifs. Par exemple, imaginez que vous écriviez un petit jeu et que vous vouliez sauvegarder la progression de l’utilisateur : nombre de points (int), niveau actuel (int), temps de jeu (double), statut de vainqueur (boolean). On peut bien sûr l’écrire en texte :

12345
5
67.5
true

Mais ce n’est ni pratique ni sûr : il faut analyser les chaînes, le format peut se dégrader, et les nombres occupent plus de place.

L’idée est d’écrire les données dans un fichier sous forme « brute » (binaire), sans conversion en texte. Pour cela, Java propose deux excellentes classes :

  • DataOutputStream — sait écrire des types primitifs dans un flux.
  • DataInputStream — sait lire des types primitifs depuis un flux.

Elles fonctionnent au-dessus des flux d’octets ordinaires (OutputStream/InputStream). Autrement dit, ce sont des « surcouches » qui ne se contentent pas d’écrire des octets, mais savent reconstituer à partir de ces octets un int, un double, un boolean et même une String.

Comment cela fonctionne-t-il ?

Un FileOutputStream classique peut être vu comme un convoyeur sur lequel vous déposez vous-même les octets un à un. Si vous souhaitez écrire un entier ou une chaîne, vous devez suivre vous-même combien d’octets chaque élément occupe.

DataOutputStream simplifie la vie : il agit comme un robot sur ce convoyeur. Vous lui dites « écris un nombre » ou « écris une chaîne », et il emballe lui-même les données dans le bon nombre d’octets et les envoie sur le disque. À l’autre bout du convoyeur, un robot similaire — DataInputStream — sait reconstituer ces octets en objets d’origine.

Pourquoi est-ce pratique ? Parce que vous n’avez pas à réfléchir au nombre d’octets pour int, double ou boolean. Les données sont stockées de façon compacte, se lisent et s’écrivent rapidement, et vous évitez les erreurs d’analyse ou les problèmes de format.

2. Exemple : écriture et lecture de types primitifs

Supposons que nous voulions sauvegarder les résultats de notre jeu (hypothétique) : le nom du joueur (String), le nombre de points (int), le meilleur temps (double), si le joueur a gagné (boolean).

Écriture des données dans un fichier

import java.io.*;

public class SaveGameData {
    public static void main(String[] args) {
        String fileName = "savegame.bin";
        String playerName = "Alice";
        int score = 12345;
        double recordTime = 67.5;
        boolean isWinner = true;

        try (DataOutputStream dos = new DataOutputStream(
                new FileOutputStream(fileName))) {
            dos.writeUTF(playerName);    // Écrire la chaîne (UTF-8)
            dos.writeInt(score);         // Écrire int (4 octets)
            dos.writeDouble(recordTime); // Écrire double (8 octets)
            dos.writeBoolean(isWinner);  // Écrire boolean (1 octet)
            System.out.println("Les données ont été enregistrées dans le fichier!");
        } catch (IOException e) {
            System.out.println("Erreur d'écriture: " + e.getMessage());
        }
    }
}
  • writeUTF(String) — écrit une chaîne au format UTF-8 (avec la longueur au début).
  • writeInt(int) — écrit 4 octets.
  • writeDouble(double) — écrit 8 octets.
  • writeBoolean(boolean) — écrit 1 octet (1 ou 0).
  • Toutes les méthodes emballent automatiquement les données dans le format adéquat.

Lecture des données depuis un fichier

import java.io.*;

public class LoadGameData {
    public static void main(String[] args) {
        String fileName = "savegame.bin";

        try (DataInputStream dis = new DataInputStream(
                new FileInputStream(fileName))) {
            String playerName = dis.readUTF();      // Lire la chaîne
            int score = dis.readInt();              // Lire int
            double recordTime = dis.readDouble();   // Lire double
            boolean isWinner = dis.readBoolean();   // Lire boolean

            System.out.println("Nom du joueur: " + playerName);
            System.out.println("Score: " + score);
            System.out.println("Temps: " + recordTime);
            System.out.println("Vainqueur: " + isWinner);
        } catch (IOException e) {
            System.out.println("Erreur de lecture: " + e.getMessage());
        }
    }
}

Point important :
L’ordre de lecture doit correspondre à l’ordre d’écriture ! Si vous avez d’abord écrit une chaîne, puis un int, puis un double, il faut lire dans le même ordre. Sinon, vous obtiendrez une erreur ou des données incohérentes.

3. Quels types sont pris en charge ?

DataOutputStream et DataInputStream prennent en charge tous les principaux types primitifs de Java :

Méthode d’écriture Méthode de lecture Type de données Taille (octets)
writeBoolean(boolean)
readBoolean()
boolean 1
writeByte(int)
readByte()
byte 1
writeShort(int)
readShort()
short 2
writeChar(int)
readChar()
char 2
writeInt(int)
readInt()
int 4
writeLong(long)
readLong()
long 8
writeFloat(float)
readFloat()
float 4
writeDouble(double)
readDouble()
double 8
writeUTF(String)
readUTF()
String (UTF) variable

Remarques :
- Pour les chaînes, on utilise le plus souvent writeUTF/readUTF (la longueur de la chaîne est écrite puis les octets en UTF-8).
- Pour écrire un tableau, commencez par écrire sa longueur, puis ses éléments un par un.

4. Exemple avancé : enregistrer des tableaux de types primitifs

Écriture d’un tableau

int[] scores = {100, 200, 300, 400, 500};
try (DataOutputStream dos = new DataOutputStream(
        new FileOutputStream("scores.bin"))) {
    dos.writeInt(scores.length); // Écrire d’abord la longueur du tableau
    for (int score : scores) {
        dos.writeInt(score);     // Puis chaque élément
    }
}

Lecture d’un tableau

try (DataInputStream dis = new DataInputStream(
        new FileInputStream("scores.bin"))) {
    int length = dis.readInt();      // Lire la longueur
    int[] scores = new int[length];
    for (int i = 0; i < length; i++) {
        scores[i] = dis.readInt();   // Lire les éléments
    }
    // Afficher le tableau
    for (int score : scores) {
        System.out.println(score);
    }
}

Pourquoi la longueur en premier ?
Parce qu’à la lecture, on ne sait pas combien de nombres ont été écrits. En enregistrant la longueur au début, on rend le format du fichier auto-documenté.

5. Points importants et particularités

Quand utiliser DataInputStream/DataOutputStream ?

  • Quand il faut sauvegarder/charger des données structurées composées de types primitifs.
  • Pour échanger des données binaires entre des programmes Java (ou même entre différents langages, si vous connaissez le format).
  • Quand la compacité et la vitesse sont importantes (par exemple, journaux, résultats de calculs, grands tableaux de nombres).

Quand ne pas les utiliser :

  • Si vous avez besoin d’un format lisible par l’humain (CSV, JSON, XML), utilisez des formats texte.
  • Pour des objets complexes avec de l’imbrication, préférez la sérialisation via ObjectOutputStream/ObjectInputStream (sujet à part).

Mise en mémoire tampon

DataOutputStream et DataInputStream ne mettent pas les données en mémoire tampon par eux-mêmes. Si vous souhaitez améliorer les performances avec de gros fichiers, encapsulez-les dans BufferedOutputStream/BufferedInputStream :

try (DataOutputStream dos = new DataOutputStream(
        new BufferedOutputStream(new FileOutputStream("data.bin")))) {
    // ...
}

Encodage des chaînes

Les méthodes writeUTF/readUTF utilisent un format spécial : d’abord la longueur de la chaîne (en octets), puis le contenu en UTF-8. À ne pas confondre avec l’écriture brute d’un tableau d’octets !

Exceptions

Les opérations de lecture/écriture peuvent lever une IOException si le fichier est inaccessible, corrompu ou s’il se termine plus tôt que prévu. En tentant de lire plus que ce qui a été écrit, on obtient souvent une EOFException. Utilisez toujours un try-with-resources ou gérez l’exception avec try-catch.

Ordre de lecture et d’écriture

L’erreur la plus fréquente — un ordre de lecture qui ne correspond pas à l’ordre d’écriture. Si vous avez écrit : int, double, boolean, mais que vous lisez double, int, boolean, vous obtiendrez des données incorrectes ou une exception.

6. Erreurs courantes

Erreur n° 1 : non-respect de l’ordre d’écriture et de lecture. Si vous changez l’ordre des méthodes, les données seront mal lues ou une exception sera levée. Par exemple, si vous avez d’abord écrit une chaîne puis un nombre, mais qu’à la lecture vous commencez par lire un nombre, vous obtiendrez une erreur de format.

Erreur n° 2 : omission de la longueur du tableau. Si vous écrivez un tableau de types primitifs sans en écrire la longueur, à la lecture vous ne saurez pas combien d’éléments lire. Cela conduit soit à une erreur, soit à des « données en trop » à la fin.

Erreur n° 3 : lecture après la fin de fichier. Si vous lisez plus de données qu’il n’en a été écrit, vous obtiendrez une EOFException (end of file).

Erreur n° 4 : utiliser DataInputStream/DataOutputStream pour des fichiers texte. Ces classes ne sont pas conçues pour lire des fichiers texte ordinaires créés, par exemple, dans le Bloc-notes. Si vous tentez de lire un tel fichier avec readInt(), vous obtiendrez des données incohérentes ou une erreur.

Erreur n° 5 : flux non fermé. Si vous n’utilisez pas try-with-resources ou ne fermez pas les flux manuellement, le fichier peut rester indisponible pour d’autres programmes ou ne pas être entièrement sauvegardé.

1
Mission
JAVA 25 SELF, niveau 36, leçon 3
Bloqué
Encodage et décodage d'un message secret ✉️
Encodage et décodage d'un message secret ✉️
1
Mission
JAVA 25 SELF, niveau 36, leçon 3
Bloqué
Inventaire des artefacts magiques 🧙
Inventaire des artefacts magiques 🧙
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION