CodeGym /Cours /C# SELF /Flux d'entrée-sortie : Str...

Flux d'entrée-sortie : Stream

C# SELF
Niveau 36 , Leçon 1
Disponible

1. Intro

Imagine une bouilloire pleine d'eau. Tu ouvres le robinet — l'eau commence à couler. Tu peux remplir la bouilloire d'un coup ou petit à petit. Pareil avec les fichiers — ce n'est pas toujours pratique ou possible de charger tout le fichier en mémoire d'un coup. Les fichiers peuvent être énormes, et parfois la source de données n'est même pas un fichier, mais par exemple une connexion réseau où les données arrivent petit à petit.

Si on essayait toujours de bosser juste avec des tableaux d'octets, sur des gros fichiers on n'aurait plus de mémoire, et pour des flux "infinis" (genre vidéo ou audio), ça ne marcherait juste pas. C'est là que la notion de flux vient à la rescousse !

En .NET, un flux c'est une abstraction pour accéder aux données de façon séquentielle : peu importe ce qu'il y a derrière — fichier, réseau, mémoire, ou même un truc chelou comme une archive compressée. Un flux te permet de lire et écrire des données par morceaux, souvent par blocs ou octets.

L'idée principale :

  • Flux — c'est un canal pour transférer des données. C'est comme un tapis roulant : tu peux "poser" (écrire) ou "prendre" (lire) des données, sans te soucier des détails de où et comment elles sont stockées.
  • Les données arrivent dans l'ordre : tu peux lire le morceau suivant seulement après le précédent (ou l'inverse si tu peux faire un "rewind").
  • La plupart du temps, tu ne gardes pas toutes les données en mémoire d'un coup (et ton ordi te dira merci !).

Cette abstraction est à la base de quasi toutes les opérations d'entrée-sortie en .NET : fichiers, réseaux, archives, même la console !

2. Les flux System.IO.Stream

Héritage et archi : System.IO.Stream

Presque tous les flux en .NET héritent de la classe abstraite System.IO.Stream. Elle définit les méthodes de base pour lire, écrire, se déplacer dans le flux et le gérer.


classDiagram
    class Stream {
        +Read()
        +Write()
        +Seek()
        +CanRead
        +CanWrite
        +CanSeek
        +Length
        +Position
    }
    class FileStream
    class MemoryStream
    class NetworkStream
    class CryptoStream
    Stream <|-- FileStream
    Stream <|-- MemoryStream
    Stream <|-- NetworkStream
    Stream <|-- CryptoStream
Schéma d'héritage des flux en .NET
  • Stream — classe de base abstraite
  • FileStream — pour bosser avec les fichiers
  • MemoryStream — pour bosser avec des données en mémoire
  • NetworkStream — pour l'interaction réseau
  • CryptoStream — pour chiffrer/déchiffrer

Petit tour des propriétés et méthodes clés d'un flux

Propriété / Méthode Description
CanRead
Est-ce qu'on peut lire depuis ce flux
CanWrite
Est-ce qu'on peut écrire dans ce flux
CanSeek
Est-ce qu'on peut se déplacer dans le flux (pas tous les flux !)
Length
Longueur du flux (si supporté — pas tous les flux)
Position
Position actuelle dans le flux
Read(...)
Lecture des données
Write(...)
Écriture des données
Seek(...)
Se déplacer dans le flux
Flush()
Vider le buffer (écrire tout ce qui est en attente dans le flux)
Close()
/
Dispose()
Fermer le flux et libérer les ressources

Voyons à quoi ça ressemble "en vrai".

3. Exemple : lire et écrire des fichiers avec Stream

Voilà un exemple minimaliste pour voir un flux "en action" :


// On ouvre un fichier pour écrire
using var stream = new FileStream("numbers.bin", FileMode.Create);

// Imaginons qu'on veut écrire les nombres de 1 à 10 dans le fichier
for (int i = 1; i <= 10; i++)
{
    byte val = (byte)i;
    stream.WriteByte(val); // On écrit un octet à la fois
}

// On ferme explicitement le fichier pour pouvoir le rouvrir en lecture
stream.Close(); 

// Maintenant on va essayer de relire ces nombres
using var stream2 = new FileStream("numbers.bin", FileMode.Open);
int value;
while ((value = stream2.ReadByte()) != -1)
{
    Console.WriteLine(value); // Affichera 1, 2, ... 10
}

Ici on utilise FileStream, qui est un vrai flux dans tous les sens du terme : tu lis et écris des données par blocs ou par octets.

Types de flux : où on peut les croiser ?

Un flux, ce n'est pas forcément juste un fichier sur le disque. Voilà quelques exemples où la notion de flux est utilisée :

  • Fichier sur le disque (genre FileStream — le cas le plus courant)
  • Flux en mémoire vive (MemoryStream — pratique pour des données temporaires ou intermédiaires)
  • Connexion réseau (NetworkStream)
  • Compression/archivage (GZipStream, DeflateStream)
  • Chiffrement (CryptoStream)
  • Entrée/sortie console (eh ouais !) — techniquement, ce sont aussi des flux

Ça te permet d'écrire du code sans te soucier de la source ou de la cible des données : si ton code bosse avec un flux, il est universel !

4. Quelques subtilités utiles

Lire et écrire, c'est des opérations où tu transfères les données par morceaux. D'habitude via des tableaux d'octets et les méthodes Read, Write.

Exemple : lecture d'un fichier par blocs

byte[] buffer = new byte[1024]; // Buffer de 1024 octets (1 Ko)
using var stream = new FileStream("bigfile.bin", FileMode.Open);

int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
    // On traite seulement bytesRead octets dans buffer
    int sum = 0;
    for (int i = 0; i < bytesRead; i++)
        sum += buffer[i];

    Console.WriteLine($"Somme du bloc : {sum}");
}

Cette approche est utilisée partout — des antivirus aux lecteurs de musique.

Se positionner dans un flux (Position, Seek)

Dans la plupart des implémentations de flux (genre les fichiers), tu peux te déplacer dans les données — lire pas juste "le prochain morceau", mais aller à une position précise et bosser à partir de là.

using var stream = new FileStream("numbers.bin", FileMode.Open);
stream.Position = 5; // On va au 6ème octet (indexé à partir de 0)
int value = stream.ReadByte();
Console.WriteLine($"6ème octet dans le fichier : {value}");

Les flux peuvent être en lecture seule, écriture seule, ou les deux

Certains flux ne supportent qu'un seul mode :

  • Fichier ouvert en écriture : seulement Write()
  • Flux pour lire des données réseau : seulement Read()
  • Dans certains cas chelous (genre un flux pour imprimer), impossible de "revenir en arrière" ou de se déplacer dans le flux.

Vérifie les opérations supportées avec les propriétés CanRead, CanWrite, CanSeek :

using var stream = new FileStream("myfile.txt", FileMode.OpenOrCreate);
if (stream.CanRead)
    Console.WriteLine("Lecture supportée");
if (stream.CanWrite)
    Console.WriteLine("Écriture supportée");
if (stream.CanSeek)
    Console.WriteLine("Déplacement dans le fichier possible");

Bufferisation dans les flux

Presque tous les flux utilisent des buffers internes pour améliorer les perfs. La bufferisation évite de faire trop d'accès disque/réseau : les données s'accumulent en interne, puis sont envoyées/écrites en une fois.

La méthode Flush() permet de vider le buffer (genre pour être sûr que tout est bien écrit sur le disque) :

using var stream = new FileStream("log.txt", FileMode.Append);
byte[] bytes = Encoding.UTF8.GetBytes("Hello, Stream!\n");
stream.Write(bytes, 0, bytes.Length);
stream.Flush(); // Assure que l'écriture est bien faite sur le disque

Si tu écris des données critiques (genre des transactions bancaires !), Flush() est ton pote.

5. Les erreurs classiques avec les flux

Très souvent, les débutants font ces erreurs :

Ils oublient de fermer le flux (et se retrouvent avec des fuites mémoire, des fichiers "bloqués" et autres galères).

Ils confondent flux texte et flux binaire — ils essaient d'écrire une chaîne avec une méthode binaire, et après ils obtiennent des "caractères bizarres".

Ils utilisent un buffer trop petit (ou pas de buffer du tout) — les opérations deviennent lentes.

Ils pensent que Read() lit toujours exactement le nombre d'octets demandé — en vrai, ça peut être moins ; il faut toujours vérifier la valeur de retour.

Ils oublient que tous les flux ne supportent pas le déplacement (Seek), surtout les flux réseau.

Par exemple :


// Mauvais exemple : lecture de tous les octets d'un fichier sans vérifier combien ont vraiment été lus
byte[] buffer = new byte[1024];
using (var stream = new FileStream("data.bin", FileMode.Open))
{
    int bytesRead = stream.Read(buffer, 0, 1024);
    // bytesRead peut être inférieur à 1024 si le fichier est plus petit !
}
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION