CodeGym /Cours /C# SELF /Classe FileStream

Classe FileStream

C# SELF
Niveau 35 , Leçon 3
Disponible

1. Classe FileStream : bosser par morceaux

Tu peux imaginer FileStream comme un tuyau d'eau direct branché sur ton fichier sur le disque. À travers ce tuyau, tu peux faire passer les données comme tu veux : envoyer des octets dans le fichier (écriture) ou recevoir des octets du fichier (lecture). Contrairement aux méthodes plus high-level qui te filent juste "un verre d'eau", FileStream te file l'accès direct au "robinet", et tu règles le débit d'eau (d'octets) comme tu veux.

FileStream bosse au niveau des octets. Ça veut dire qu'il s'en fiche que ce soit du texte ou une image ; pour lui, tout est juste une séquence d'octets. Si tu bosses avec des fichiers texte via FileStream, tu dois toi-même convertir les chaînes en octets (en utilisant une encodage, genre UTF-8) à l'écriture, et faire l'inverse à la lecture.

Quand est-ce qu'il faut utiliser FileStream ?

  • Bosser avec des fichiers énormes : Quand le fichier est tellement gros que tu peux pas (ou c'est pas malin) de le charger en entier en RAM (genre des logs de plusieurs Go, des vidéos). FileStream te permet de lire ou écrire par morceaux (chunks), donc tu utilises la mémoire beaucoup plus efficacement.
  • Données binaires : Si tu bosses avec des fichiers qui sont pas juste du texte (images, audio, vidéo, objets sérialisés, fichiers de base de données), FileStream c'est l'outil principal, parce qu'il te donne l'accès direct aux octets.
  • Contrôle précis des modes d'accès : Tu veux ouvrir un fichier en lecture *et* écriture en même temps ? Ou l'ouvrir pour que personne d'autre puisse y toucher ? Ou au contraire, que plusieurs process puissent lire en même temps ? FileStream te donne tout le contrôle sur ces modes.
  • Opérations asynchrones : Dans les applis modernes qui doivent être rapides (genre les serveurs web), c'est super important que les opérations sur les fichiers ne bloquent pas le thread principal. FileStream gère les méthodes asynchrones (ReadAsync, WriteAsync), donc ton appli reste réactive.
  • Lecture/écriture partielle ou accès aléatoire : Si tu dois lire des données à un endroit précis du fichier (genre à partir du 500ème octet) ou écrire au milieu du fichier, FileStream te laisse gérer la position du curseur dans le fichier.

Erreur classique des débutants : Utiliser FileStream pour les trucs les plus simples, genre écrire une seule ligne de texte dans un petit fichier. Là, c'est overkill. FileStream c'est pour les scénarios spécifiques et un peu plus velus.

2. Créer et ouvrir un FileStream

Pour commencer à bosser avec un fichier via FileStream, tu dois créer une instance. Ça se fait avec le constructeur, qui prend plusieurs paramètres importants pour dire comment tu veux interagir avec le fichier.

using System;
using System.IO;   // Obligatoire pour FileStream
using System.Text; // Pour gérer les encodages (conversion string <-> bytes)

Exemple de lecture basique d'un fichier texte avec FileStream :

D'abord, on crée un fichier pour avoir quelque chose à lire.

// On crée un fichier texte simple pour la démo
string path = "example_filestream.txt";

// On ouvre le stream en lecture.
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

// On crée un buffer (tableau d'octets) pour stocker temporairement les données lues.
byte[] buffer = new byte[fs.Length];

// On lit les octets du stream dans le buffer.
int bytesRead = fs.Read(buffer, 0, buffer.Length);

// On convertit les octets lus en string, avec l'encodage qu'il faut (genre UTF-8).
string content = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("Lu depuis le fichier via FileStream : " + content);

fs.Close(); // on ferme le stream

Paramètres principaux

  • FileMode : comment ouvrir le fichier (Open, Create, Append OpenOrCreate etc.)
  • FileAccess : quoi faire avec le fichier (Read, Write, ReadWrite)
  • FileShare : est-ce que d'autres process peuvent utiliser ce fichier en même temps (pour les trucs simples, t'en as pas besoin en général)

Erreur classique : Si tu oublies de fermer FileStream, le fichier peut rester "locké" — tu pourras plus le supprimer ou le rouvrir !

3. Paramètres du constructeur FileStream

Le constructeur de FileStream te permet de régler super précisément comment tu veux ouvrir le fichier. Voilà les paramètres principaux :

new FileStream(
    string path,           // Chemin du fichier (absolu ou relatif)
    FileMode mode,         // Comment ouvrir le fichier (créer, ouvrir, écraser, etc.)
    FileAccess access,     // Quels droits (lecture seule, écriture seule, lecture+écriture)
    FileShare share        // Comment les autres process peuvent accéder au fichier pendant qu'il est ouvert (optionnel)
);

Regardons les valeurs des enums FileMode et FileAccess :

FileMode (Mode d'ouverture du fichier) :

Ça dit à l'OS comment gérer le fichier à l'ouverture.

  • FileMode.Open : Ouvre un fichier existant. Si le fichier n'existe pas à ce chemin, ça balance une FileNotFoundException.
  • FileMode.Create : Crée un nouveau fichier. Si un fichier avec ce nom existe déjà, il sera complètement écrasé (tout son contenu sera supprimé).

FileAccess (Droits d'accès au fichier) :

Ça définit ce que ton programme peut faire avec le fichier ouvert.

  • FileAccess.Read : Fichier ouvert juste pour la lecture. Tu peux pas écrire dedans.
  • FileAccess.Write : Fichier ouvert juste pour écrire. Tu peux pas lire dedans.
  • FileAccess.ReadWrite : Fichier ouvert pour lire et écrire. C'est le mode le plus flexible, mais aussi le plus tricky, parce que tu dois gérer la position dans le stream.

FileShare (Partage du fichier) :

Ça dit comment les autres process peuvent ouvrir le même fichier pendant qu'il est ouvert par ton programme. Super important pour éviter les blocages.

  • FileShare.Read : Les autres process peuvent lire le fichier pendant qu'il est ouvert par ton FileStream, mais pas écrire.
  • FileShare.Write : Les autres process peuvent écrire dans le fichier pendant qu'il est ouvert par ton FileStream, mais pas lire.
  • FileShare.ReadWrite : Les autres process peuvent lire et écrire dans le fichier. C'est le mode le plus permissif, mais ça peut créer des conflits si plusieurs programmes modifient le fichier en même temps.

Plus de détails sur les modes dans la prochaine conf.

4. Lire et écrire des données avec FileStream

Quand tu bosses avec FileStream, tu manipules des octets. Donc pour les données texte, tu dois convertir entre string et tableau d'octets, en utilisant les classes d'encodage (genre System.Text.Encoding.UTF8).

Écrire des données dans un fichier avec FileStream

Pour écrire, tu convertis tes données (genre une string) en tableau d'octets, puis tu balances ces octets dans le stream.

string outputPath = "user_data.txt";
string userName = "Ivan Petrov";

// On convertit la string en tableau d'octets avec l'encodage UTF-8
byte[] userNameBytes = Encoding.UTF8.GetBytes(userName);

// On ouvre le FileStream en écriture.
FileStream fsWrite = new FileStream(outputPath, FileMode.Create, FileAccess.Write);

// On écrit le tableau d'octets dans le stream.
fsWrite.Write(userNameBytes, 0, userNameBytes.Length);
Console.WriteLine($"Le nom '{userName}' a été écrit dans le fichier '{outputPath}'.");

fsWrite.Close(); // on ferme le stream

Lire des données depuis un fichier avec FileStream

Pour lire, tu récupères les octets du stream dans un buffer, puis tu reconvertis ces octets dans le format que tu veux (genre une string).

string inputPath = "user_data.txt";

// On ouvre le FileStream en lecture
FileStream fsRead = new FileStream(inputPath, FileMode.Open, FileAccess.Read);

// On crée un buffer de la taille du fichier pour lire tous les octets.
byte[] buffer = new byte[fsRead.Length];

// On lit les octets du stream dans le buffer.
int bytesRead = fsRead.Read(buffer, 0, buffer.Length);

// On convertit les octets lus en string.
string loadedUserName = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Nom depuis le fichier (via FileStream) : {loadedUserName}");

fsRead.Close(); // on ferme le stream

5. Bosser avec des fichiers énormes

Le gros avantage de FileStream par rapport aux méthodes File.ReadAll... — c'est que tu peux lire et écrire le fichier par morceaux, ce qui est super important pour les gros fichiers qui tiennent pas en RAM.

Lire des gros fichiers par morceaux

Quand tu lis un fichier par morceaux, tu utilises un buffer (tableau d'octets) de taille fixe et tu lis dedans jusqu'à la fin du fichier.

string bigFilePath = "bigfile.bin";     // gros fichier
int bufferSize = 4096;                  // Taille du buffer, genre 4 Ko
byte[] buffer = new byte[bufferSize];   // On crée le buffer
int bytesRead;                          // Nombre d'octets effectivement lus
long totalBytesRead = 0;                // Total des octets lus

// on ouvre le stream
FileStream fs = new FileStream(bigFilePath, FileMode.Open, FileAccess.Read);
int chunkNumber = 1;

// La boucle continue tant que fs.Read() renvoie un nombre positif d'octets
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
    totalBytesRead += bytesRead;
    Console.WriteLine($"Chunk {chunkNumber++} : lu {bytesRead} octets. Total : {totalBytesRead} octets.");

    // !!! Ici tu traites les "morceaux" de données lus !!!
   
}

fs.Close(); // on ferme le stream

Note importante : La méthode fs.Read(buffer, offset, count) ne garantit pas qu'elle va remplir tout le buffer. Elle renvoie le nombre d'octets effectivement lus, qui peut être inférieur à count (surtout à la fin du fichier). Utilise toujours la valeur de retour bytesRead pour traiter les données correctement.

Écrire de grosses données par morceaux

Comme pour la lecture, tu peux écrire de gros volumes de données dans un fichier par morceaux.

string bigOutputPath = "big_output.txt";
string longText = new string('A', 1_000_000); // String d'un million de 'A'
byte[] longTextBytes = Encoding.UTF8.GetBytes(longText);
int writeBufferSize = 1024; // On écrit 1 Ko à la fois

// FileMode.Create : on crée le fichier
FileStream fsWrite = new FileStream(bigOutputPath, FileMode.Create, FileAccess.Write);

for (int i = 0; i < longTextBytes.Length; i += writeBufferSize)
{
    // On calcule combien d'octets il reste à écrire à cette itération
    int bytesToWrite = Math.Min(writeBufferSize, longTextBytes.Length - i);

    // On écrit une portion de longTextBytes, à partir de l'offset 'i'
    fsWrite.Write(longTextBytes, i, bytesToWrite);
    Console.WriteLine($"Écrit {bytesToWrite} octets");
}

fsWrite.Close(); // on ferme le stream

6. Pièges pas évidents et erreurs classiques

Même avec un outil aussi puissant que FileStream, y'a des subtilités à connaître :

Bufferisation et Flush() : Comme déjà dit, les données peuvent rester dans le buffer en mémoire sans aller sur le disque. Si tu utilises pas using et que tu n'appelles pas Close()/Dispose(), et que tu fermes juste le programme, tu peux perdre des données. Appelle toujours Flush() (ou compte sur using/Close()) pour être sûr que tout est bien écrit.

Gestion des exceptions : Les erreurs d'entrée/sortie (genre IOException si tu veux écrire sur un disque plein, UnauthorizedAccessException si t'as pas les droits, FileNotFoundException si tu veux ouvrir un fichier qui existe pas en mode Open) — c'est courant. Mets toujours tes opérations FileStream dans des blocs try-catch.

Encodages : Quand tu bosses avec des données texte via FileStream (qui bosse avec des octets), c'est à toi de choisir le bon encodage (Encoding.UTF8, Encoding.ASCII, Encoding.Unicode etc.) pour convertir string <-> octets. Si tu te plantes d'encodage, tu vas avoir des "caractères bizarres".

Gestion de la position : Si tu utilises Seek(), fais gaffe aux paramètres offset et origin, pour pas te retrouver hors du fichier ou à un endroit chelou.

Verrous de fichiers (FileShare) : Si tu ouvres un fichier avec FileShare.None, les autres process pourront pas y accéder. Ça peut être relou si un autre programme (ou même un autre thread dans ton appli) veut utiliser le même fichier. Choisis toujours le mode FileShare le plus adapté.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION