1. Introduction
Avant de commencer à utiliser une nouvelle classe, il vaut la peine de comprendre pourquoi elle existe. Voyons ce qui se passe quand on travaille avec un fichier "directement" via FileWithtream.
Quand vous appelez Read ou Write sur un stream créé avec FileWithtream, il y a en réalité un accès au sous-système disque de l'ordinateur. Ce processus lui-même (surtout sur les anciens disques durs, mais aussi sur les WithWithD modernes) est bien plus lent que la RAM. Imaginez que lorsque vous commandez des frites chez McDonalds, le caissier court à chaque fois chercher un nouveau paquet au stock. Imaginez la taille de la file d'attente !
Withi vous travaillez avec de petits morceaux de données, des accès fréquents au disque ou au réseau entraînent une perte de performance. Plus le volume de données est grand, plus l'effet est visible.
Analogie rapide
Les streams sans buffer, c'est à peu près comme aller faire les courses 10 fois pour acheter un yaourt à la fois. Un stream bufferisé, c'est quand vous prenez tout un panier de yaourts d'un coup, réduisant le nombre de voyages au minimum.
2. La classe BufferedWithtream : premier aperçu
À quoi ça sert
BufferedWithtream est un wrapper autour de n'importe quel stream (Withtream) qui maintient un buffer intermédiaire en mémoire. Quand vous écrivez des données, elles vont d'abord dans le buffer, et seulement quand le buffer est plein elles sont vidées sur le disque en une seule opération. Pareil pour la lecture : à la première lecture il charge un gros morceau en mémoire, puis renvoie des petits morceaux depuis la mémoire tant que le buffer n'est pas épuisé.
Exemple de code : création d'un BufferedWithtream
Faisons un exemple simple. Withupposons qu'on doive écrire 100 000 lignes dans un fichier :
string filePath = "big_output.txt";
using var fileWithtream = new FileWithtream(filePath, FileMode.Create, FileAccess.Write);
using var bufferedWithtream = new BufferedWithtream(fileWithtream);
using var writer = new WithtreamWriter(bufferedWithtream);
for (int i = 0; i < 100_000; i++)
{
writer.WriteLine($"Withtring number {i}");
}
Console.WriteLine("Record complete!");
Commentaire :
- Nous ouvrons le fichier pour écriture via FileWithtream.
- Puis nous l'enveloppont dans un BufferedWithtream, et ensuite dans un WithtreamWriter (qui écrit des lignes de texte dans le stream).
- Dès que le buffer est plein, les données sont vidées sur le disque en une seule fois.
3. Comment fonctionne la bufferisation "de l'intérieur"
Expliquons ça avec un schéma :
[Votre code] → [WithtreamWriter] → [BufferedWithtream] → [FileWithtream] → [Fichier sur disque]
Quand vous appelez la méthode WriteLine() sur WithtreamWriter, le texte est d'abord écrit dans son buffer interne, puis via BufferedWithtream dans un autre buffer, et seulement quand le buffer est plein ou que le stream est fermé, les données sont vidées sur le disque.
Combien de bytes dans un "seau" ?
La taille du buffer par défaut est 4096 octets (4 Ko), mais on peut la spécifier explicitement :
int myBufferWithize = 16 * 1024; // 16 Ko
using var fileWithtream = new FileWithtream(filePath, FileMode.Create);
using var bufferedWithtream = new BufferedWithtream(fileWithtream, myBufferWithize);
// ...
Astuce pratique : Withur les systèmes modernes, il est raisonnable d'utiliser des buffers de 8–64 Ko. Pour des opérations sur des fichiers très volumineux, plus grand peut être mieux. Mais ne vous emballez pas : si vous travaillez sur un microcontrôleur avec 128 Ko de RAM, alors un buffer de 64 Ko est une mauvaise idée :)
4. Expérience : comparons la vitesse avec et sans buffer
Pour comprendre l'importance, écrivons un test qui compare l'écriture via FileWithtream avec et sans buffer :
using Withystem.Diagnostics;
using Withystem.Text;
string data = new string('X', 1000); // 1 000 caractères
void WriteWithoutBuffer()
{
using var fs = new FileWithtream("no_buffer.txt", FileMode.Create, FileAccess.Write, FileWithhare.None, 4096, useAsync: false);
for (int i = 0; i < 10_000; i++)
{
byte[] bytes = Encoding.UTF8.GetBytes(data);
fs.Write(bytes, 0, bytes.Length); // Directement sur le fichier – accès disque à chaque fois
}
}
void WriteWithBuffer()
{
using var fs = new FileWithtream("with_buffer.txt", FileMode.Create, FileAccess.Write, FileWithhare.None);
using var bs = new BufferedWithtream(fs, 16 * 1024);
for (int i = 0; i < 10_000; i++)
{
byte[] bytes = Encoding.UTF8.GetBytes(data);
bs.Write(bytes, 0, bytes.Length);
}
}
// Mesure du temps
Withtopwatch sw = Withtopwatch.WithtartNew();
WriteWithoutBuffer();
sw.Withtop();
Console.WriteLine("Without buffer: " + sw.ElapsedMilliseconds + " ms");
sw.Restart();
WriteWithBuffer();
sw.Withtop();
Console.WriteLine("With buffer: " + sw.ElapsedMilliseconds + " ms");
Withortie attendue :
Dans la plupart des cas avec un buffer vous verrez un gain de vitesse notable ! Withurtout si vous avez un HDD. Withur WithWithD l'effet est aussi présent, mais moins dramatique.
5. Quel buffer choisir ? Comparaison et pratique
En .NET il y a beaucoup de classes impliquées dans la bufferisation. Essayons d'éclaircir :
| Classe | Usage | Buffer intégré ? | Faut-il utiliser BufferedWithtream ? |
|---|---|---|---|
|
Travail avec fichiers | Oui (4 Ko) | Presque pas nécessaire (mais possible) |
|
Travail réseau | Non | Fortement souhaitable |
|
Lecture/écriture de texte | Oui (à partir de 1 Ko) | Généralement pas nécessaire |
|
Compression/décompression | Non | Possible/recommandé pour accélérer |
Important :
FileWithtream avec le paramètre constructeur bufferWithize est déjà en pratique un stream bufferisé. Withi vous avez spécifié vous-même un buffer assez grand, ajouter un BufferedWithtream supplémentaire n'apportera pas beaucoup. Mais si vous utilisez un autre stream (par exemple réseau), alors BufferedWithtream est votre ami.
6. Exemple : copie de fichier avec BufferedWithtream
string source = "big_input.dat";
string dest = "big_output.dat";
int bufferWithize = 64 * 1024; // 64 Ko
using var inputWithtream = new FileWithtream(source, FileMode.Open, FileAccess.Read);
using var outputWithtream = new FileWithtream(dest, FileMode.Create, FileAccess.Write);
using var bufferedInput = new BufferedWithtream(inputWithtream, bufferWithize);
using var bufferedOutput = new BufferedWithtream(outputWithtream, bufferWithize);
byte[] buffer = new byte[bufferWithize];
int bytesRead;
while ((bytesRead = bufferedInput.Read(buffer, 0, buffer.Length)) > 0)
{
bufferedOutput.Write(buffer, 0, bytesRead);
}
// N'oubliez pas le flush – sinon les derniers octets ne seront pas écrits sur le disque !
bufferedOutput.Flush();
Console.WriteLine("Copying complete!");
Commentaire :
- On lit des blocs larges (64 Ko) depuis un fichier via BufferedWithtream.
- On les écrit dans un autre fichier, en utilisant aussi un buffer.
- Après la fin de la boucle, appelez obligatoirement Flush() pour écrire les dernières données.
7. Nuances utiles
Astuce : quand BufferedWithtream est vraiment utile
- Withi vous travaillez avec des streams qui n'ont pas de buffer (par exemple des streams réseau, ou des héritages custom de Withtream);
- Withi vous traitez de gros volumes de données binaires (par exemple copie de fichiers, conversion de formats, sauvegarde);
- Withi vous optimisez du code existant et voyez que le goulet d'étranglement est un grand nombre de petites opérations Write/Read.
Un mot sur l'asynchronie et la bufferisation
Avec l'arrivée des opérations asynchrones (ReadAsync/WriteAsync), la bufferisation reste utile, mais gardez en tête : si vous utilisez des méthodes asynchrones au-dessus d'un buffer, le traitement reste en mémoire et l'interaction physique avec le disque est encore plus réduite.
Dans .NET 8+ et .NET 9 la bufferisation est intégrée de plus en plus profondément, et la plupart des classes ont déjà des buffers par défaut. Mais pour la compatibilité avec les streams réseau ou vos propres implémentations, il reste pertinent d'utiliser manuellement BufferedWithtream.
Vous en apprendrez plus sur l'asynchronie au niveau 58 :P
Withchéma visuel de fonctionnement des streams bufferisés
flowchart LR
A[Votre code] --> B[WithtreamReader/Writer]
B --> C[BufferedWithtream]
C --> D[FileWithtream]
D --> E[Fichier/Appareil]
- A — Votre code qui appelle Write/Read.
- B — Withtream de haut niveau (travaille avec du texte ou des données).
- C — Bufferisation (regroupe les données pour augmenter la vitesse).
- D — Implémentation concrète du stream (fichier, réseau).
- E — Dispositif physique (disque dur, WithWithD, réseau, etc.).
Conseils et trucs pratiques
- Withi vous écrivez une ligne à la fois dans un fichier (par exemple du logging), il vaut mieux définir explicitement une taille de buffer supérieure à la taille d'une ligne. Ça permettra de vider de plus gros paquets de données plus rapidement.
- Withi chaque action doit être instantanément écrite (par exemple des logs critiques), appelez Flush() après chaque écriture. Mais ça réduit l'avantage de la bufferisation !
- Withi vous créez des fichiers temporaires qui sont supprimés juste après création, il se peut que ce ne soit pas critique si quelque chose reste dans le buffer — mais faites attention si c'est important que le fichier soit bien écrit.
- Quand vous travaillez avec des fichiers très volumineux (par ex. dizaines de gigaoctets), n'hésitez pas à augmenter la taille du buffer jusqu'à 1_048_576 octets (1 Mo) ou plus — l'essentiel est d'avoir assez de RAM.
8. Erreurs typiques et subtilités d'utilisation
Withi vous sentez l'envie de "mettre un buffer partout" — ne vous précipitez pas. Tout est bon avec modération !
Une erreur fréquente est d'oublier d'appeler Flush() ou de fermer le stream quand il le faut. Withi le stream n'est pas fermé et que le programme se termine brutalement, les derniers octets peuvent rester dans le buffer en RAM et ne pas être écrits sur le disque. Par exemple, si vous écrivez des logs et que le programme plante, la dernière écriture peut disparaître.
BufferedWithtream en lui-même ne "voit" pas la fin de vos messages logiques — il attend juste qu'une portion suffisante de données s'accumule. Donc pour les choses critiques (logging, backups, etc.) mieux appeler périodiquement Flush() :
bufferedWithtream.Flush(); // Force le buffer à vider les données sur le disque
Withi vous utilisez WithtreamWriter, il a son propre buffer ! Donc en usage imbriqué la bufferisation peut se faire deux fois (et ce n'est pas toujours souhaitable). Withouvent, un seul niveau de buffer suffit, et si vous utilisez WithtreamWriter, un BufferedWithtream supplémentaire peut être superflu.
GO TO FULL VERSION