1. Giriş
Təsəvvür elə ki, yenə də qarşımızda mətn faylının məzmununu oxumaq tapşırığı var. Deyək ki, proqramçılardan maraqlı sitatlar olan bir faylımız var və onları artıq yaxşı tanıdığımız mətn tətbiqimizdə göstərmək istəyirik. Bəli, artıq File.ReadAllText kimi yüksək səviyyəli metodları görmüşük, amma bəs fayl böyükdürsə və onu tam yox, sətr-sətr oxumaq lazımdırsa? Yaxud faylı idarəli şəkildə oxumaq istəyirik, məsələn, sətrləri yavaş-yavaş yükləmək, yaddaşı doldurmamaq üçün?
Bax, məhz burada StreamReader köməyə gəlir — bu sinif mətn fayllarını sətr-sətr, blok-blok, simvol-simvol oxumaq üçün əladır — yəni, çevik və rahatdır.
StreamReader necə işləyir?
StreamReader — System.IO namespace-indən olan bir sinifdir, hansı ki, axından mətn məlumatlarını oxumağı həyata keçirir (adətən bu fayldır, amma şəbəkə axını və ya yaddaş da ola bilər). O, məlumatları lazım olan kodlaşdırmada oxuya bilir, onları simvollara və sətrlərə çevirir və sənə rahat data tipləri qaytarır: string və char.
Bunu sxem kimi göstərsək, təxminən belə alınır:
[ Fayl (baytlar) ] --(FileStream)--> [ StreamReader (simvol kodlarını ayırır) ] ---> [ Sənin kodun (string, char) ]
- Fayl (və ya başqa bayt mənbəyi): Bizə baytlar verir.
- FileStream: Bu baytları "kanal" kimi oxuyur.
- StreamReader: Baytları kodlaşdırmaya uyğun simvol və sətrlərə çevirir (default — UTF-8).
Niyə həmişə təkcə File.ReadAllText istifadə etmək olmaz?
Praktikada fayllar böyük ola bilər. Hətta çox böyük (məsələn, loglar və ya milyon sətrlik csv). Belə faylları tam yaddaşa yükləməyə çalışmaq — bir oturuma bütün tortu yeməyə bənzəyir: dadlıdır, amma sağlamlıq üçün təhlükəlidir.
StreamReader faylı "parça-parça" oxuyur. O, bütün faylı yaddaşa yükləmir, sənin sorğunuza uyğun növbəti sətri və ya simvolu yalnız lazım olanda verir. Bu, yaddaşa qənaət edir və çox rahatdır.
2. Faylı tamamilə sətr-sətr oxumaq
quotes.txt adlı fayl yaradaq və ora belə sətrlər yazaq:
Kod — bu poeziyadır.
Debug — bu detektivdir.
"İşləmir" — ən yaxşı bug-report.
İndi isə kodumuzu artıq başlatdığımız tədris tətbiqinə əlavə edək (sadə konsol proqramı olsun, onu tədricən inkişaf etdiririk). Faylı .exe-nin yanında qoyuruq — proqram onu oxuyub sətr-sətr ekrana çıxarsın.
Şərhlərlə kod:
// Proqramın qovluğunda faylın yolunu alırıq
string fileName = "quotes.txt";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
// Faylın mövcudluğunu yoxlayırıq
if (!File.Exists(filePath))
{
Console.WriteLine($"Fayl tapılmadı: {filePath}");
return;
}
// Faylı oxumaq üçün StreamReader açırıq
using StreamReader reader = new StreamReader(filePath);
string? line;
int lineNumber = 1;
// Faylı sətr-sətr sona qədər oxuyuruq
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine($"{lineNumber,2}: {line}");
lineNumber++;
}
Burada nə baş verir?
- Faylın yolunu Path.Combine və AppDomain.CurrentDomain.BaseDirectory ilə hesablayırıq — beləliklə kod Windows, Linux və hətta Docker-də eyni işləyir.
- Əvvəlcə faylın həqiqətən mövcud olub-olmadığını yoxlayırıq.
- using ilə StreamReader yaradırıq və faylı bir-bir sətr oxuyuruq (ReadLine() metodu).
- using blokunun sonunda fayl bağlanır, hətta əllə bağlamağı unutsan və ya exception tutsan belə.
Vizualizasiya: iş sxemi
[ quotes.txt ] --(FileStream)--> [ StreamReader ] --(ReadLine)--> [ string line ] --(Console.WriteLine)--> Ekran
^
|
AppDomain.CurrentDomain.BaseDirectory + Path.Combine
3. İstifadə xüsusiyyətləri və nüanslar
Sətrlərin oxunması necə gedir
ReadLine() metodu yeni sətr simvoluna (\n və ya \r\n) qədər olan sətri qaytarır. Fayl bitəndə isə null qaytarır. Ona görə dövr adətən belə olur:
string? line;
while ((line = reader.ReadLine()) != null)
{
// sətrin işlənməsi
}
StreamReader ilə işləyərkən tipik səhvlər
Bəzən using istifadə etməmək istəyi olur, sadəcə belə yazırlar:
StreamReader reader = new StreamReader(filePath);
// ...
reader.Close();
Belə yazmaq — qışda qapını açıq qoymağa bənzəyir: hər şey ola bilər. Oxuma zamanı səhv çıxsa (məsələn, diskə çıxış birdən yox olsa), faylı bağlamaq üçün kod işə düşməyəcək və fayl sistemdə açıq qalacaq.
Ona görə də using istifadə etmək — sadəcə tövsiyə yox, real zərurətdir. Gələcəkdə sənin sysadmin dostun təşəkkür edəcək ki, proqramın arxada "asılı" fayllar buraxmır.
using declaration ilə qısa yazılış
C#-ın yeni versiyalarında (8.0-dan başlayaraq) daha qısa yazmaq olar:
using StreamReader reader = new StreamReader(filePath);
string? line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
Dispose cari blokun (metodun) sonunda çağırılacaq.
4. Faydalı nüanslar
Naməlum kodlaşdırmalı faylı oxumaq
Default olaraq StreamReader UTF-8 ilə işləyir. Amma bəzən başqa kodlaşdırmada fayllar olur (məsələn, köhnə rus mətnləri üçün Windows-1251). Belə halda proqram "?" və ya oxunmaz simvollar göstərə bilər.
Kodlaşdırmanı açıq göstərmək olar:
using System.Text;
// Faylı Windows-1251 kimi açırıq
using StreamReader reader = new StreamReader(filePath, Encoding.GetEncoding("windows-1251"));
Kodlaşdırmalar və Encoding haqqında sənəd
Faylı bir böyük mətn kimi oxumaq
Bəzən bütün faylı bir mətn kimi almaq lazımdır, amma File.ReadAllText yox, StreamReader ilə.
using StreamReader reader = new StreamReader(filePath);
string allText = reader.ReadToEnd();
Console.WriteLine(allText);
- ReadToEnd() metodu faylın cari mövqedən sonuna qədər bütün məzmunu bir sətr kimi oxuyur.
- Böyük fayllar üçün bu səmərəsiz ola bilər və OutOfMemoryException riski var. Bir neçə MB-lıq fayllar üçün — tam işlək variantdır.
Bu real həyatda necə istifadə olunur
- Logların işlənməsi. Server log faylın varsa, onu sətr-sətr oxumaq, lazım olan hadisələri filtrləmək və hamısını yaddaşa yükləməmək rahatdır.
- CSV import. Böyük cədvəlləri parse etmək lazımdırsa, sətr-sətr məlumatı çıxarıb işləmək rahatdır.
- Böyük mətn fayllarında açar sözlərin yoxlanması. Milyonlarla sətr arasında lazım olan mətn axtara bilərsən, yaddaş limiti barədə narahat olmadan.
- Unit testlər. Test dataları olan fayllar tez-tez StreamReader ilə sətr-sətr oxunur.
Oxuma metodlarının müqayisəsi
| Metod | Nə vaxt istifadə etməli | Üstünlüklər | Çatışmazlıqlar |
|---|---|---|---|
|
Kiçik fayllar | Bir sətr kod, tez | Böyük fayl – çox yaddaş |
|
İstənilən, xüsusən böyük fayllar | Sətr-sətr oxumaq, az yaddaş | Bir az çox kod, məntiq qurmaq çətindir |
|
Kiçik/orta fayllar | Kodlaşdırmaya çevik nəzarət | Çox böyük fayllarla çətindir |
5. Praktika
Gəlin tapşırığı bir az çətinləşdirək. Proqramımız fayldan yalnız ilk N sətri çıxarsın, bu sayını istifadəçi daxil edir. Razılaş ki, bəzən yüzlərlə sətri görmək istəmirsən — motivasiya üçün bir neçə sitat kifayətdir. :)
Bax belə etmək olar:
using System;
using System.IO;
class Program
{
static void Main()
{
string fileName = "quotes.txt";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
if (!File.Exists(filePath))
{
Console.WriteLine($"Fayl tapılmadı: {filePath}");
return;
}
Console.Write("Neçə sətr çıxarılsın? ");
string? input = Console.ReadLine();
if (!int.TryParse(input, out int linesToShow) || linesToShow < 1)
{
Console.WriteLine("Xəta: Düzgün 0-dan böyük rəqəm daxil et.");
return;
}
using StreamReader reader = new StreamReader(filePath);
int current = 0;
string? line;
while (current < linesToShow && (line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
current++;
}
if (current == 0)
Console.WriteLine("Fayl boşdur və ya ilk sətri oxumaq mümkün olmadı.");
else if (current < linesToShow)
Console.WriteLine($"Faylda cəmi {current} sətr var idi.");
}
}
6. Vacib məqamlar və tipik tələlər
StreamReader ilə işləyəndə əsas — resursları düzgün idarə etməkdir (bax əvvəlki mühazirəyə: IDisposable). Bunu unutsaq, "fayl artıq başqa proses tərəfindən istifadə olunur" və ya "çox açıq fayl var" xətası ala bilərik.
Daha bir incəlik — kodlaşdırmaya diqqət et. Əgər faylın UTF-8 olduğuna əmin deyilsənsə, lazım olan kodlaşdırmanı açıq göstər. Bunun üçün StreamReader konstruktorunda ikinci parametr var.
Daha bir praktik "tələ" — fayl proqram işləyərkən dəyişirsə (məsələn, log faylı). Fayldan yeni sətrləri dərhal görmək olmaya bilər, əgər fayl başqa proseslər tərəfindən istifadə olunursa — belə halda faylı yenidən oxumaq və ya xüsusi metodlardan istifadə etmək lazımdır, amma bu barədə sonra danışacağıq.
GO TO FULL VERSION