CodeGym /Kurslar /C# SELF /Böyük fayllarla işləməyi optimallaşdırmaq

Böyük fayllarla işləməyi optimallaşdırmaq

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

1. Giriş

Kiçik fayllarla işləyəndə çox vaxt heç nə hiss etmirsən: yazdın, oxudun — və unudursan. Amma faylın ölçüsü ən azı 100–500 MB-ı keçəndə, üstəlik gigabaytlara çatanda maraqlı effektlər çıxır:

  • Əməliyyatlar yavaşıyır, xüsusən də onları "birbaşa" etmək — məsələn, File.ReadAllBytes() və ya File.WriteAllText() birdən proqramı ləngidə bilər.
  • RAM çatmaya bilər, OutOfMemoryException yaranır.
  • Sistem swap istifadə etməyə başlayır, bu isə bütün sistemi yavaşlada bilər.
  • Paralel əməliyyatlar diskə artıq yüklənmə gətirə bilər.

Real tapşırıqlarda bu tez-tez olur:

  • Server logları (gün ərzində gigabaytlardır).
  • Böyük CSV və ya XML faylların emalı — export-import.
  • Video, audio, arxivlər, binary fayllarla işləmək.
  • Böyük assemblylərin və ya backup-ların köçürülməsi.

2. Böyük fayllarla işləyərkən optimallaşdırma strategiyaları

Optimallaşdırmağa başlamazdan əvvəl qərar verək nəyi sürətləndirmək və ya yaxşılaşdırmaq istəyirik. Ən yayılmış tapşırıqlar:

  • “Faylı maksimum sürətlə oxu/yaz və sistemi məhv etmə”.
  • “Faylı hissələrlə emal et ki, yaddaşı doldurmayasan”.
  • “Yaddaşda əlavə kopyalar yaratma”.
  • “Böyük faylları paralel emal et (əgər mümkünsə)”.

Ümumi yanaşmalar:

  • Axın üzrə oxuma və yazma: stream və bufferlərdən istifadə et, hissələrlə oxu/yaz (FileStream, BufferedStream).
  • Faylla birbaşa disk üzərində əməliyyatlar — yaddaşda əlavə nüsxələr olmadan.
  • Bufferlərə və yaddaşa ehtiyatlı yanaş: bütün faylı RAM-də saxlamama.
  • Asinxron əməliyyatlar — əsas thread-i bloklamamaq üçün (daha sonra müzakirə olunacaq).

3. Axın üzrə oxuma və yazma: əsas pattern

Əsas prinsip: Fayllarla hissələrlə işləyin! Müasir C#-da bu FileStream, BufferedStream və ümumiyyətlə stream sinifləri ilə asanlıqla edilir.


// Faylı oxumaq üçün açırıq:
using FileStream fs = new FileStream("bigfile.bin", FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[1024 * 1024]; // 1 MB
int bytesRead;

// Faylın sonuna qədər oxuyuruq
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
    // Burada oxunan məlumatı emal edirik!
    // Məsələn, bütün baytların cəmini hesablayarıq (sadəcə məşq üçün)
    long sum = 0;
    for (int i = 0; i < bytesRead; i++)
        sum += buffer[i];

    Console.WriteLine($"Oxundu {bytesRead} bayt, cəmi: {sum}");
}

Məsləhət: Buffer ölçüsünü (məsələn, 64 KB, 128 KB, 1 MB) təcrübə ilə seç. Həddən kiçikdirsə — diskə çox çağırış olacaq. Həddən böyükdürsə — həmişə daha yaxşı nəticə verməyə bilər, amma yaddaşı sarf edir.

Niyə belə? File.ReadAllBytes() və ya File.ReadAllText() kimi klassik funksiyalar faylı hissələrə bölmədən yaddaşa yükləməyə çalışır. Fayl çox böyükdürsə — nəticə aydındır: OutOfMemoryException.

4. BufferedStream: nə üçün və nə vaxt

Bu siniflə əvvəlki mühazirədə tanış olmuşduq, amma xatırladaq: BufferedStream — istənilən digər stream-in üzərinə qoyulan wrapper-dir, byte-by-byte oxuma/yazma əvəzinə bloklarla işləməyə imkan verir.

İstifadə nümunəsi:


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)
{
    // Məlumatın emalı
}

Bəzi hallarda BufferedStream istifadə etmək sürəti yaxşılaşdırır, xüsusən də faylı bir neçə baytla oxuyursunuzsa və fayl sistemi blok əməliyyatlarını sevirsə.

Maraqlı fakt: Yeni .NET versiyalarında FileStream-də artıq daxili buffer mövcuddur, ona görə də BufferedStream xüsusilə "xam" streamlərdə (məsələn, şəbəkə və ya qeyri-standart cihazlar) böyük fayda verir.

5. Böyük mətn fayllarının oxunması və yazılması

Binary fayllarla hər şey aydındır: hissələrlə oxu və yaz. Amma mətn faylları, xüsusən böyük CSV, log və JSON faylları necə emal etmək lazımdır?

Bu halda StreamReader oxumaq üçün, StreamWriter yazmaq üçün köməyə gəlir.

Sətr-sətr oxuma:


using var reader = new StreamReader("biglog.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
    // Sətrin emalı
    if (line.Contains("ERROR"))
        Console.WriteLine("Aşkarlanıb ERROR: " + line);
}

Niyə bu yaxşıdır?

  • Biz bütün faylı yaddaşda saxlamırıq.
  • StreamReader daxilindəki buffer artıq optimal şəkildə tənzimlənib.

Sətrlərlə yazma:


using var writer = new StreamWriter("output.txt");
for (int i = 0; i < 1000000; i++)
    writer.WriteLine($"Bu sətr nömrəsidir {i}");

Axın üzrə oxumanın memarlığı


   [Diskdə fayl]
          |
     [FileStream]
          |
  [BufferedStream (opsional)]
          |
   [StreamReader/StreamWriter (mətn üçün)]
          |
   [Sənin kodun: məlumatın emalı]

6. Praktik tətbiq

Log faylları ilə işləyən tətbiqimizi inkişaf etdirək. Gəlin köhnə logları (7 gündən köhnə) sadəcə köçürməklə yox, bir arxivə sıxışdıraq. Fayl böyükdürsə — oxu və yazma hissələrlə edəcəyik ki, yaddaşı yükləməyək.

Sadə surətdə streamlərlə kopyalama istifadə edək:


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 KB
    int bytesRead;
    while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
    {
        destStream.Write(buffer, 0, bytesRead);
        // Burada progress-bar əlavə edə bilərsən!
    }
}

Harada istifadə olunur?

  • Backup-ların kopyalanması
  • Günlərə və ya aylara görə logların birləşdirilməsi
  • Faylların əvvəlcədən emalı (məsələn, sətrlərin filtrasiyası)

7. Bufferin effektivliyini necə qiymətləndirmək

Bəzən bilmək istəyirsən: "Nə qədər sürətlənib?" Bunu vaxt ölçərək yoxlamaq olar:


var watch = System.Diagnostics.Stopwatch.StartNew();
CopyLargeFile("source.bin", "dest.bin");
watch.Stop();
Console.WriteLine($"Kopyalama vaxtı: {watch.Elapsed.TotalSeconds} saniyə");

Buffer üçün tipik dəyərlər:

  • 4 KB — fayl sisteminin minimal bloku.
  • 64 KB/128 KB — praktikada demək olar ki, həmişə yaxşı işləyir.
  • 1 MB və yuxarı — yalnız çox sürətli SSD və böyük fayllar üçün məntiqlidir.

Müxtəlif buffer ölçülərini özün yoxla!

8. Böyük fayllarda axtarış və emal

Əgər sadəcə kopyalamaq yox, müəyyən sətri, ədədi və ya ifadəni tapmaq lazımdırsa — böyük fayllarda bunu hissələrlə etmək ən effektivdir.

Nümunə: böyük logda bütün ERROR sətrlərini tap


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); // Nəticəyə yalnız lazım olan sətrləri yazırıq
}

Bu yanaşma gigabaytlarla log emal etməyə imkan verir, çox yaddaş sərf etmədən.

9. Çox böyük fayllarla işləmənin xüsusiyyətləri (>2 GB)

.NET və Windows axın üzrə oxuma istifadə etdikdə istənilən ölçüdə fayllarla yaxşı işləyir (bəzi hallarda terabaytlar da), amma bəzi nüanslar var!

  • 32-bit tətbiqlər 2 GB adreslə məhdudlaşır — x64 istifadə et!
  • Böyük fayllar üçün həmişə 64-bit platformadan (AnyCPU və ya x64) istifadə et.
  • 4 GB-dan böyük fayllar üçün fayl sistemi FAT32 uyğun deyil — NTFS/exFAT lazımdır.

Böyük faylların iterativ emalı


+------------------+
|    Start         |
+------------------+
        |
        v
+------------------------------+
| Oxumaq üçün stream aç        |
+------------------------------+
        |
        v
+------------------------------+
| Faylın sonuna qədər dövr     |
+------------------------------+
        |
        v
+---------------------------+
| Blok oxu                  |
+---------------------------+
        |
        v
+---------------------------+
| Bloku emal et            |
+---------------------------+
        |
        v
+--------------------------+
| Növbəti blok             |
+--------------------------+
        |
        v
+--------------------+
| Stream bağla       |
+--------------------+

10. Faydalı nüanslar

Fayl kimi stream: FileStream sinfinin əsas metodları

Metod Təsvir
Read
Faylın bir hissəsini bufferə oxuyur
Write
Buffer-in bir hissəsini fayla yazır
Seek
Faylda istənilən mövqeyə jump etməyə imkan verir
Length
Faylın baytla ölçüsü
Position
Fayldakı indiki mövqe

Seek() istifadə nümunəsi:


using var stream = new FileStream("bigfile.bin", FileMode.Open);
// 1 GB qabağa gedək!
stream.Seek(1024L * 1024 * 1024, SeekOrigin.Begin);

byte[] buffer = new byte[1024];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
// İndi faylın ortasından məlumat oxuyuruq!

Harada lazım ola bilər?

  • Faylın indeksləşdirilməsi
  • İstənilən bloklara sürətli çıxış (məsələn, böyük verilənlər bazalarında)

Bir neçə thread ilə faylla işləmək (multi-threading)

Əgər tapşırıq imkan verirsə, böyük faylları paralel emal etmək olar: faylı bloklara böl və müxtəlif hissələri eyni vaxtda oxu/yaz. Amma yadda saxla ki, HDD təsadüfi girişdə yavaş işləyir, SSD isə xeyli sürətlidir.

Ev tapşırıqlarında əmin deyilsənsə, sadə ardıcıl işləmə ilə başla. Paralelləşdirmə daha çox müxtəlif faylları eyni anda emal etmək üçün yararlıdır, tək faylı paralel emal etmək həmişə faydalı olmaya bilər.

11. Böyük fayllarla işləməkdə tipik səhvlər

Səhv №1: bütün faylı yaddaşa oxumaq.
Yeni başlayanlar çox vaxt File.ReadAllBytes() və ya File.ReadAllText() istifadə edir, hətta böyük fayllar üçün. Fayl gigabaytlardırsa, tətbiq sadəcə xətayla bağlanacaq — yaddaş çatışmazlığı. Axın üzrə oxu istifadə et.

Səhv №2: çox kiçik buffer istifadə etmək.
Çox kiçik buffer ilə oxumaq — supu çay qaşığı ilə içməyə bənzəyir. Proqram disk çağırışlarında vaxt itirəcək. Məqbul buffer ölçüsünü seç.

Səhv №3: stream-i bağlamağı unutmaq.
Əgər stream-i iş bitdikdən sonra bağlamazsan, fayl deskriptoru tutulub qalacaq. Bu digər proqramlara mane olur və OS səviyyəsində səhvlərə səbəb ola bilər. Həmişə using istifadə et — bu daha təhlükəsiz və təmizdir.

Səhv №4: eyni fayla eyni vaxtda giriş.
Eyni faylı proqramın müxtəlif hissələrindən oxumaq və yazmaq IOException-ə aparır. Bəzən "işləyir" kimi görünə bilər, amma stabil iş əldə etməzsən.

1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
Verilənlərin bufferləşdirilməsi prinsipi
Input-output-un optimallaşdırılması
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION