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 |
|---|---|
|
Faylın bir hissəsini bufferə oxuyur |
|
Buffer-in bir hissəsini fayla yazır |
|
Faylda istənilən mövqeyə jump etməyə imkan verir |
|
Faylın baytla ölçüsü |
|
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.
GO TO FULL VERSION