CodeGym /Kurslar /C# SELF /BufferedStream-dən istifadə

BufferedStream-dən istifadə

C# SELF
Səviyyə , Dərs
Mövcuddur

1. Giriş

Yeni bir sinifi istifadə etməyə başlamazdan əvvəl onun niyə lazım olduğunu anlamalısınız. Gəlin faylla "birbaşa" FileStream vasitəsilə işləyəndə nə baş verdiyini anlamağa çalışaq.

Siz FileStream ilə yaradılmış stream üçün Read və ya Write çağırdıqda, əslində kompüterin disk subsisteminə müraciət baş verir. Bu proses (xüsusən köhnə HDD-lərdə, amma yeni SSD-lərdə də) operativ yaddaşla işləməkdən xeyli yavaşdır. Təsəvvür edin ki, McDonalds-da kartof-fri sifariş edəndə kassir hər dəfə anbarın yanına qaçıb yeni paket gətirir. Nəticədə növbə necə uzun olar!

Kiçik parçalarda işləsəniz, diska və ya şəbəkəyə tez-tez müraciətlər performans itkisinə gətirir. Məlumat həcmi nə qədər böyükdür — effekt bir o qədər nəzərə çarpır.

Qısa analoqiya

Belə çıxır ki, bufersiz stream-lər mağaza üçün 10 dəfə gedib hər dəfə bir yoqurt almağa bənzəyir. Buferli stream isə bir dəfəyə bütün səbəti götürüb səfərləri minimuma endirmək kimidir.

2. BufferedStream sinifi: ilk baxış

Nəyə görə lazımdır

BufferedStream — hər hansı bir streamin (Stream) üzərində olan wrapper-dir, hansı ki, yaddaşda müvəqqəti bufer saxlayır. Yazdığınız zaman məlumatlar ilk öncə bufera düşür və yalnız bufer dolduqda — diska böyük bir əməliyyatla yazılır. Oxumaqda da oxşardır: ilk oxunuşda o əhəmiyyətli bir parça məlumatı yaddaşa yükləyir, sonra isə yaddaşdan hissə-hissə qaytarır, ta ki bufer tükənənə qədər.

Nümunə kod: BufferedStream yaratmaq

Gəlin sadə bir nümunə yaradaq. Tutaq ki, məqsədimiz fayla 100 000 sətir yazmaqdır:


string filePath = "big_output.txt";
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
using var bufferedStream = new BufferedStream(fileStream);
using var writer = new StreamWriter(bufferedStream);

for (int i = 0; i < 100_000; i++)
{
    writer.WriteLine($"Stroka nomer {i}");
}

Console.WriteLine("Zapic zavershena!");

Şərh:

  • Biz faylı yazmaq üçün FileStream vasitəsilə açırıq.
  • Sonra onu BufferedStream ilə bükürük, və daha sonra — StreamWriter ilə (o mətn sətrlərini stream-ə yazır).
  • Bufer dolan kimi — məlumatlar diska bir partiya ilə yazılır.

3. Buferləşmə "daxildən" necə işləyir

Gəlin bunu sxem ilə izah edək:


[Sizinkod] → [StreamWriter] → [BufferedStream] → [FileStream] → [Fayl disklə]

Siz StreamWriter-in WriteLine() metodunu çağıranda, mətn əvvəlcə daxili buferə yazılır, sonra BufferedStream vasitəsilə — daha bir buferə, və yalnız bufer dolduqda və ya stream bağlandıqda, məlumatlar diska yazılır.

Bir vedrədə neçə bayt var?

Default bufer ölçüsü 4096 bayt (4 KB)-dır, amma onu açıq şəkildə göstərə bilərsiniz:


int myBufferSize = 16 * 1024; // 16 KB
using var fileStream = new FileStream(filePath, FileMode.Create);
using var bufferedStream = new BufferedStream(fileStream, myBufferSize);
// ...

Praktik məsləhət: Müasir sistemlərdə 8–64 KB arası buferləri istifadə etmək məntiqlidir. Çox böyük fayl əməliyyatları üçün — daha da böyük. Amma həddindən artıq böyütməyin: əgər mikrokontrollerdə 128 KB RAM varsa, 64 KB bufer pis fikirdir :)

4. Eksperiment: buferlə və bufersiz sürəti müqayisə edək

Əhəmiyyətini anlamaq üçün FileStream ilə buferli və bufersiz yazmanın sürətini müqayisə edən test yazaq:


using System.Diagnostics;
using System.Text;

string data = new string('X', 1000); // 1 000 simvol

void WriteWithoutBuffer()
{
    using var fs = new FileStream("no_buffer.txt", FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: false);
    for (int i = 0; i < 10_000; i++)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(data);
        fs.Write(bytes, 0, bytes.Length); // Birbaşa fayla – hər dəfə diskə müraciət
    }
}

void WriteWithBuffer()
{
    using var fs = new FileStream("with_buffer.txt", FileMode.Create, FileAccess.Write, FileShare.None);
    using var bs = new BufferedStream(fs, 16 * 1024);
    for (int i = 0; i < 10_000; i++)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(data);
        bs.Write(bytes, 0, bytes.Length);
    }
}

// Vaxtı ölçürük
Stopwatch sw = Stopwatch.StartNew();
WriteWithoutBuffer();
sw.Stop();
Console.WriteLine("Bez bufera: " + sw.ElapsedMilliseconds + " ms");

sw.Restart();
WriteWithBuffer();
sw.Stop();
Console.WriteLine("S buferom: " + sw.ElapsedMilliseconds + " ms");

Gözlənilən çıxış:
Çox hallarda buferlə sürətdə əhəmiyyətli artım görəcəksiniz! Xüsusən HDD varsa. SSD-də də effekt var, amma o qədər dramatik olmaya bilər.

5. Hansı bufer seçilməlidir? Müqayisə və praktika

.NET-də buferləşdirmə üçün bir çox sinif var. Aydınlıq gətirək:

Sinif Necə istifadə olunur Bufer daxildədirmi? BufferedStream istifadə etmək lazımdır?
FileStream
Fayllarla işləmək Bəli (4 KB-dən) Demək olar ki, lazım deyil (amma ola bilər)
NetworkStream
Şəbəkə işi Xeyr Çox məsləhətlidir
StreamReader/Writer
Mətn oxuma/yazma Bəli (təxminən 1 KB-dən) Adətən lazım deyil
GZipStream
Sıxma/çıxarma Xeyr Sürətləndirmək üçün ola/vaxtı gələndə lazımdır

Vacib:
FileStream-in konstruktorunda bufferSize parametri əslində artıq buferləşdirilmiş stream-dir. Əgər siz kifayət qədər böyük bufer göstərmisinizsə, əlavə BufferedStream böyük fayda verməyə bilər. Amma başqa növ stream-lərlə (məsələn, şəbəkə) işləyirsinizsə, BufferedStream sizin dostunuzdur.

6. Nümunə: BufferedStream ilə faylın kopyalanması


string source = "big_input.dat";
string dest = "big_output.dat";

int bufferSize = 64 * 1024; // 64 KB

using var inputStream = new FileStream(source, FileMode.Open, FileAccess.Read);
using var outputStream = new FileStream(dest, FileMode.Create, FileAccess.Write);
using var bufferedInput = new BufferedStream(inputStream, bufferSize);
using var bufferedOutput = new BufferedStream(outputStream, bufferSize);

byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = bufferedInput.Read(buffer, 0, buffer.Length)) > 0)
{
    bufferedOutput.Write(buffer, 0, bytesRead);
}
// Flush etməyi unutmayın – yoxsa son baytlar diska düşməyə bilər!
bufferedOutput.Flush();

Console.WriteLine("Kopirovanie zaversheno!");

Şərh:

  • Bir fayldan böyük bloklarla (64 KB) BufferedStream vasitəsilə oxuyuruq.
  • Onları başqa fayla yazırıq, yenə buferdən istifadə edərək.
  • Döngü bitdikdən sonra son məlumatların yazılması üçün mütləq Flush() çağırın.

7. Faydalı incəliklər

Məsləhət: BufferedStream həqiqətən nə vaxt lazımdır

  • Əgər buferi olmayan stream-lərlə işləyirsinizsə (məsələn, şəbəkə stream-ləri və ya özünəməxsus Stream törəmələri);
  • Əgər böyük həcmdə binary məlumatlarla işləyirsinizsə (məsələn, faylların kopyalanması, format çevrilməsi, ehtiyat nüsxə);
  • Əgər mövcud kodu optimizasiya edirsinizsə və dar nöqtə çoxlu kiçik Write/Read əməliyyatlarıdır.

Asinxronluq və buferləşmə haqqında qısa

ReadAsync/WriteAsync kimi asinxron əməliyyatlar çıxdıqdan sonra da buferləşmə faydalıdır, amma nəzərə alın: əgər asinxron metodları bufer üzərində istifadə edirsinizsə, emal yenə yaddaş daxilində gedir və fiziki diskə qarşılıqlı təsir daha da azaldılır.

.NET 8+ və .NET 9-da buferləşmə daha dərin inteqrasiya olunub və əksər siniflər artıq default olaraq buferlərə malikdir. Amma şəbəkə stream-ləri və ya öz xüsusi implementasiyalar üçün hələ də BufferedStream-i əl ilə istifadə etmək faydalıdır.

Asinxronluq haqqında daha ətraflı 58-ci səviyyədə öyrənəcəksiniz :P

Buferləşdirilmiş stream-lərin vizual sxemi

flowchart LR
    A[Sizinkod] --> B[StreamReader/Writer]
    B --> C[BufferedStream]
    C --> D[FileStream]
    D --> E[Fayl/Qurğu]
  • A — Sizinkod, Write/Read çağırır.
  • B — Yüksək səviyyəli stream (mətn və ya data ilə işləyir).
  • C — Buferləşmə (sürəti artırmaq üçün məlumatları qruplaşdırır).
  • D — Stream-in konkret implementasiyası (fayl, şəbəkə).
  • E — Fiziki qurğu (HDD, SSD, şəbəkə və s.).

Praktik məsləhətlər və trükklər

  1. Əgər fayla bir sətir-bir sətir yazırsınızsa (məsələn, logging), bufer ölçüsünü bir sətrdən böyük olaraq açıq göstərmək daha yaxşıdır. Bu, böyük partiyalarla daha sürətli yazmağa imkan verər.
  2. Əgər hər əməliyyat dərhal yazılmalıdır (məsələn, kritik loglar), hər yazımdan sonra Flush() çağırın. Amma bu buferləşmənin üstünlüyünü azaldır!
  3. Əgər müvəqqəti fayllar yaradırsınız və dərhal silinirsə, buferdə nə qaldığı o qədər də əhəmiyyətli olmaya bilər — amma əgər faylın mütləq yazılması vacibdirsə, diqqətli olun.
  4. Çox böyük fayllar (onlarla GB) ilə işləyərkən bufer ölçüsünü 1_048_576 bayt (1 MB) və daha yuxarıya qaldırmaqdan çəkinməyin — əsas odur ki, kifayət qədər RAM olsun.

8. Tipik səhvlər və istifadə incəlikləri

İndi hər yeri "buferlə doldurma" həvəsi hiss etdinizsə — tələsməyin. Hər şeyin yaxşısı miqdarda!

Tez-tez edilən səhvlərdən biri — Flush() çağırmağı və ya stream-i bağlamağı unutmaqdır. Əgər stream hələ bağlıdır və proqram qəfil dayandırılıbsa, son baytlar buferdə qala bilər və diska düşməyə bilər. Məsələn, proqram çöksə, son log yazısı itə bilər.

BufferedStream özü sizin loqik mesajlarınızın sonunu "görmür" — o sadəcə müəyyən ölçüdə data yığılmasını gözləyir. Buna görə kritik məsələlər (loglama, ehtiyat nüsxə və s.) üçün periodik olaraq Flush() çağırmaq daha məsləhətlidir:

bufferedStream.Flush(); // Buferi diska yükləməyi məcbur edir

Əgər StreamWriter istifadə edirsinizsə, onun öz buferi var! Yəni nested istifadə etdikdə ikiqat buferləşmə ola bilər (və bu həmişə yaxşı deyil). Çox vaxt bir səviyyə bufer kifayətdir, və əgər StreamWriter istifadə edirsinizsə, əlavə BufferedStream lazım olmaya bilər.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION