1. Introduction
Quand tu bosses avec des petits fichiers, tu ne remarques souvent rien : tu écris, tu lis — et puis c'est tout. Mais dès que la taille d'un fichier dépasse ne serait-ce que 100–500 Mo, et encore plus quand c'est plusieurs gigas, des effets intéressants apparaissent :
- Les opérations deviennent plus lentes, surtout si tu fais tout "à la brute" — par exemple, File.ReadAllBytes() ou File.WriteAllText() peuvent brusquement ralentir tout le programme.
- La mémoire vive peut manquer, et tu peux tomber sur OutOfMemoryException.
- Le système commence à utiliser le swap, ce qui peut ralentir toute la machine.
- Les opérations parallèles peuvent créer une surcharge excessive sur le disque.
Dans la vraie vie, ça arrive souvent :
- Logs de serveurs (des gigaoctets par jour).
- Traitement de gros fichiers CSV ou XML pour export/import.
- Travail avec de la vidéo, de l'audio, des archives, des fichiers binaires.
- Copie de grosses assemblies ou sauvegardes.
2. Stratégies d'optimisation pour gros fichiers
Avant de te jeter dans l'optimisation — définissons d'abord ce que tu veux accélérer ou améliorer. Voilà les tâches les plus fréquentes :
- "Lire/écrire un fichier le plus vite possible sans planter le système".
- "Traiter le fichier par morceaux pour ne pas surcharger la mémoire".
- "Ne pas créer de copies inutiles des données en mémoire".
- "Traiter les gros fichiers en parallèle (si possible)".
Approches générales :
- Lecture/écriture en streaming : utilise des streams et des buffers, lis/écris par morceaux (FileStream, BufferedStream).
- Opérations directes sur le disque — sans copies intermédiaires en mémoire.
- Gère soigneusement les buffers et la mémoire : ne garde pas tout le fichier en RAM.
- Opérations asynchrones — si tu veux éviter de bloquer le thread principal (on en parle dans la prochaine leçon).
3. Lecture/écriture en streaming : pattern de base
Principe principal : Travaille les fichiers par morceaux ! En C# moderne c'est simple avec FileStream, BufferedStream et tous les streams en général.
// Ouvrons le stream pour lecture :
using FileStream fs = new FileStream("bigfile.bin", FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[1024 * 1024]; // 1 Mo
int bytesRead;
// On lit jusqu'à la fin du fichier
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
// Traite les données lues ici !
// Par exemple, calculons la somme de tous les octets (juste pour l'exercice)
long sum = 0;
for (int i = 0; i < bytesRead; i++)
sum += buffer[i];
Console.WriteLine($"Lu {bytesRead} octets, somme : {sum}");
}
Conseil : La taille du buffer (par ex. 64 Ko, 128 Ko, 1 Mo) se choisit empiriquement. Trop petit — trop d'accès disque. Trop grand — pas forcément plus rapide, et ça consomme de la mémoire.
Pourquoi ? Les méthodes classiques comme File.ReadAllBytes() ou File.ReadAllText() sans découpage essaient de charger tout le contenu en mémoire. Si le fichier est énorme — résultat garanti : OutOfMemoryException.
4. BufferedStream : pourquoi et quand l'utiliser
On a déjà vu cette classe auparavant, mais rappel : BufferedStream est un wrapper autour d'un autre stream qui permet de lire/écrire par blocs au lieu d'octet par octet.
Exemple d'utilisation :
using var fileStream = new FileStream("bigfile.bin", FileMode.Open, FileAccess.Read);
using var bufferedStream = new BufferedStream(fileStream, 1024 * 128);
byte[] buffer = new byte[1024 * 128];
int bytesRead;
while ((bytesRead = bufferedStream.Read(buffer, 0, buffer.Length)) > 0)
{
// Traitement des données
}
Parfois l'utilisation de BufferedStream accélère, surtout si tu lis quelques octets à la fois et que le système de fichiers préfère des accès par blocs.
Petit fait : Dans les versions récentes de .NET, FileStream a déjà de la buffering intégrée, donc BufferedStream apporte le plus de gain avec des streams "bruts", par ex. réseau ou dispositifs non standard.
5. Lecture et écriture de gros fichiers texte
Avec les fichiers binaires c'est clair : on lit/écrit par blocs. Mais pour les fichiers texte, surtout gros CSV, logs, JSON ?
Ici viennent en aide StreamReader pour la lecture et StreamWriter pour l'écriture.
Lecture ligne à ligne :
using var reader = new StreamReader("biglog.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
// Traitement de la ligne
if (line.Contains("ERROR"))
Console.WriteLine("ERROR détecté : " + line);
}
Pourquoi c'est bien ?
- On ne garde pas tout le fichier en mémoire.
- Le buffering interne de StreamReader est déjà configuré de façon optimale.
Écriture ligne par ligne :
using var writer = new StreamWriter("output.txt");
for (int i = 0; i < 1000000; i++)
writer.WriteLine($"Ceci est la ligne numéro {i}");
Architecture de la lecture en streaming
[Fichier sur le disque]
|
[FileStream]
|
[BufferedStream (optionnel)]
|
[StreamReader/StreamWriter (pour le texte)]
|
[Votre code : traitement des données]
6. Mise en pratique
Continuons avec notre application de gestion de logs. Imaginons qu'on archive les vieux logs (plus vieux que 7 jours) en les compressant dans une archive. Si un fichier est énorme — on le lira et écrira par morceaux pour ne pas "casser" la mémoire.
Pour simplifier, faisons une copie standard via des streams :
void CopyLargeFile(string sourcePath, string destPath)
{
using var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read);
using var destStream = new FileStream(destPath, FileMode.Create, FileAccess.Write);
byte[] buffer = new byte[1024 * 256]; // 256 Ko
int bytesRead;
while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
{
destStream.Write(buffer, 0, bytesRead);
// On peut ajouter une barre de progression ici !
}
}
Où c'est utile ?
- Copie de sauvegardes
- Fusion de logs par jour ou mois
- Prétraitement de fichiers (par ex. filtrage de lignes)
7. Comment évaluer l'efficacité du buffer ?
Parfois tu veux savoir : "Et c'est devenu combien plus rapide ?" Facile à tester en mesurant le temps d'exécution :
var watch = System.Diagnostics.Stopwatch.StartNew();
CopyLargeFile("source.bin", "dest.bin");
watch.Stop();
Console.WriteLine($"Temps de copie : {watch.Elapsed.TotalSeconds} secondes");
Tailles de buffer typiques :
- 4 Ko — plus petit bloc typique du système de fichiers.
- 64 Ko/128 Ko — fonctionne bien dans la plupart des cas.
- 1 Mo et plus — justifié seulement sur des SSD très rapides et pour de très gros fichiers.
Teste différentes tailles de buffer toi-même !
8. Recherche et traitement de données dans de gros fichiers
Si tu veux non seulement copier, mais chercher une ligne, un nombre, une expression — le plus efficace est de le faire par morceaux.
Exemple : trouver toutes les lignes contenant des erreurs dans un énorme log
using var reader = new StreamReader("server.log");
using var writer = new StreamWriter("errors.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
if (line.Contains("ERROR"))
writer.WriteLine(line); // On n'envoie que les lignes utiles dans le résultat
}
Cette approche permet de traiter des logs de plusieurs gigaoctets sans avaler toute la mémoire.
9. Particularités du travail avec des fichiers énormes (>2 Go)
.NET et Windows gèrent très bien des fichiers de grande taille (même des dizaines de téraoctets) si tu utilises la lecture en streaming. Mais il y a des subtilités !
- Les applis 32 bits sont limitées à 2 Go d'adressage mémoire — utilise x64 !
- Pour les gros fichiers, utilise toujours une plateforme 64 bits (AnyCPU ou x64).
- Pour des fichiers > 4 Go, le système de fichiers FAT32 ne convient pas — prends NTFS/exFAT.
Traitement itératif des gros fichiers
+------------------+
| Start |
+------------------+
|
v
+------------------------------+
| Ouvrir le stream en lecture |
+------------------------------+
|
v
+------------------------------+
| Tant que fin du fichier non |
| atteinte |
+------------------------------+
|
v
+---------------------------+
| Lire un bloc de données |
+---------------------------+
|
v
+---------------------------+
| Traiter le bloc |
+---------------------------+
|
v
+--------------------------+
| Bloc suivant |
+--------------------------+
|
v
+--------------------+
| Fermer le stream |
+--------------------+
10. Astuces utiles
Le fichier comme stream : méthodes clés de FileStream
| Méthode | Description |
|---|---|
|
Lit une partie du fichier dans un buffer |
|
Écrit une partie du buffer dans le fichier |
|
Permet de sauter à la position voulue dans le fichier |
|
Taille du fichier en octets |
|
Position actuelle dans le fichier |
Exemple d'utilisation de Seek() :
using var stream = new FileStream("bigfile.bin", FileMode.Open);
// On se déplace de 1 Go vers l'avant !
stream.Seek(1024L * 1024 * 1024, SeekOrigin.Begin);
byte[] buffer = new byte[1024];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
// On lit maintenant des données depuis le milieu du fichier !
Où ça sert ?
- Indexation de fichiers
- Accès rapide aux blocs nécessaires (par ex. dans de grandes bases de données)
Traitement multi-thread sur des fichiers
Si la tâche le permet, on peut traiter les gros fichiers en parallèle : par ex. les découper en blocs et lire/écrire différents morceaux en même temps. Mais il faut comprendre que les disques durs (HDD) sont lents en accès aléatoire, alors que les SSD sont bien plus rapides.
Pour des usages simples à la maison, si tu n'es pas sûr, travaille séquentiellement. Le parallélisme est surtout utile pour traiter plusieurs fichiers en même temps, pas forcément un seul fichier (le gain peut être faible).
11. Erreurs typiques en travaillant avec de gros fichiers
Erreur n°1 : on lit tout le fichier en mémoire.
Les débutants utilisent souvent File.ReadAllBytes() ou File.ReadAllText() même pour des fichiers énormes. Si le fichier pèse des gigaoctets, l'application plante — pas assez de mémoire. Utilise la lecture en streaming.
Erreur n°2 : utiliser un buffer trop petit.
Lire avec un buffer minuscule, c'est comme boire la soupe à la petite cuillère. Le programme passera son temps à attendre des accès disque. Choisis une taille de buffer raisonnable.
Erreur n°3 : oublier de fermer le stream.
Si tu ne fermes pas le stream après usage, le descripteur de fichier reste occupé. Ça gêne d'autres programmes et peut causer des erreurs au niveau OS. Utilise toujours using — c'est plus sûr et plus propre.
Erreur n°4 : accès simultané au même fichier.
Tenter de lire et écrire le même fichier depuis différentes parties du programme est une bonne façon d'obtenir une IOException. Même si parfois "ça marche", tu n'auras pas de stabilité.
GO TO FULL VERSION