1. Giriş
Gəlin xatırlayaq, FileReader və FileWriter necə işləyir. Onlarda read() və ya write() metodunu hər çağırdığınızda fayl sisteminə müraciət olunur — yəni kompüter həqiqətən diskə gedib bir simvol oxuyur və ya yazır. Fayl böyükdürsə və siz onu hər dəfə bir simvol olmaqla oxuyur və ya yazırsınızsa, bu minlərlə, hətta milyonlarla disk müraciətinə çevrilir. Disk isə, məlum olduğu kimi, proqramçının ən sürətli dostu deyil.
Bunu belə təsəvvür edin: sizə vedrədən butulkaya su tökmək lazımdır və bunu çay qaşığı ilə edirsiniz. Formal olaraq — işləyir, amma çox ləngdir. Daha məntiqlisi kiçik bir qapaq və ya stəkan götürməkdir. Fayllarla da eyni: bir simvol-bir simvol oxumaq və ya yazmaq suyu qaşıqla daşımağa bənzəyir.
Bu belə görünür:
// Faylı simvol-simvol oxuma ( "çay qaşığı ilə"! )
try (FileReader reader = new FileReader("big.txt")) {
int c;
while ((c = reader.read()) != -1) {
// simvolu emal edirik (məsələn, sadəcə sayırıq)
}
}
Fayl böyükdürsə, proqramın olduqca ləng işlədiyini görəcəksiniz.
Buferləşdirmə: nədir və nə üçün lazımdır
Bufer — xüsusi yaddaş sahəsidir (adətən massiv), haraya ki, məlumatlar bir-bir simvol yox, dərhal böyük hissələr (məsələn, 8 KB və ya daha çox) şəklində yüklənir və ya yazılır. Sadə dildə, bufer — yaddaşdan bir parça, aralıq vedrə kimidir: məlumatlar oraya bir-bir simvol deyil, böyük porsiyalarla oxunur və ya yazılır.
Bu belə işləyir: oxuyarkən proqram diskə bir dəfə müraciət edir, buferə bütöv məlumat bloku yükləyir və daha sonra onları yaddaşdan sizə bir-bir simvol şəklində verir. Blok bitən kimi növbətisi yüklənir. Yazarkən də eyni məntiq: məlumatlar əvvəlcə buferdə toplanır, sonra isə bir böyük hissə kimi diskə göndərilir (və ya dərhal, əgər siz flush() çağırmısınızsa).
Bu niyə daha sürətlidir? Çünki disk özlüyündə yavaşdır, xüsusən də xırda-xırda müraciətlər ediləndə. Amma ona daha az müraciət edib, bir dəfəyə daha çox götürsəniz, hər şey nəzərəçarpacaq dərəcədə sürətli işləyir. Başqa sözlə: buferləşdirmə diskə müraciətlərin sayını azaldır və proqramı sürətləndirir.
2. BufferedReader və BufferedWriter: sintaksis və nümunələr
Java-da mətn fayllarını oxuma və yazmanı buferləşdirmək üçün iki sinifdən istifadə olunur:
- BufferedReader — mətn fayllarını oxumaq üçün.
- BufferedWriter — mətn fayllarını yazmaq üçün.
Onlar adi Reader/Writer-in (məsələn, FileReader/FileWriter) üzərində işləyir və buferləşdirmə əlavə edirlər.
BufferedReader ilə faylı sətir-sətir oxuma
Ən çox rast gəlinən ssenari — faylı sətir-sətir oxumaqdır. readLine() metodu yeni sətir simvoluna qədər ("\n" və ya "\r\n") olan sətiri qaytarır.
import java.io.*;
public class BufferedReaderExample {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line); // Sətiri ekrana çıxarırıq
}
} catch (IOException e) {
System.out.println("Faylı oxuyarkən xəta: " + e.getMessage());
}
}
}
Burada nə baş verir?
Biz FileReader yaradırıq ki, faylı simvol-simvol oxuya bilir, sonra onu BufferedReader ilə örtürük. Buferləşdirilmiş oxuyucu məlumatları bir-bir simvol yox, böyük hissələrlə götürür, yaddaşda saxlayır və readLine() metodu ilə bizə sətir-sətir verir. Nəticədə siz sadəcə while dövrü yazırsınız, sətirləri bir-bir alırsınız və fayl nə qədər böyük olursa-olsun, oxuma yenə də sürətli və səmərəli olur.
BufferedWriter ilə fayla yazma
Sətirləri fayla effektiv şəkildə yazmaq da mümkündür:
import java.io.*;
public class BufferedWriterExample {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("Hello, world!");
writer.newLine(); // Yeni sətir (OS-dən asılıdır)
writer.write("Bu ikinci sətirdir.");
} catch (IOException e) {
System.out.println("Faylı yazarkən xəta: " + e.getMessage());
}
}
}
Burada nə baş verir?
Əvvəlcə FileWriter yaradırıq, sonra onu BufferedWriter ilə örtürük. write() və ya newLine() çağırdığınızda, məlumat dərhal diskə getmir. Onlar buferdə — xüsusi yaddaş qatında toplanır. Bufer dolanda, yaxud axını bağlayanda (və ya açıq şəkildə flush() çağırdığınızda), yığılan mətn bir dəfəyə fayla yazılır. Bu yanaşma yazmanı əhəmiyyətli dərəcədə sürətləndirir və diskə müraciətləri azaldır.
Bütün bunlar bir tətbiqdə necə görünür?
Tutaq ki, fayla qeydləri saxlayan və onları ekrana çıxaran sadə bir “günlük” proqramı yazırıq.
import java.io.*;
import java.util.Scanner;
public class DiaryApp {
public static void main(String[] args) {
String fileName = "diary.txt";
Scanner scanner = new Scanner(System.in);
// Yeni qeydin yazılması
System.out.print("Günlüyə yeni qeyd daxil edin: ");
String entry = scanner.nextLine();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true))) {
writer.write(entry);
writer.newLine();
System.out.println("Qeyd saxlanıldı!");
} catch (IOException e) {
System.out.println("Yazma zamanı xəta: " + e.getMessage());
}
// Bütün qeydlərin oxunması
System.out.println("\nSizin günlüyünüz:");
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("Oxuma zamanı xəta: " + e.getMessage());
}
}
}
Biz "diary.txt" fayl adını götürür və istifadəçiyə yeni qeyd daxil etməyi təklif edirik. Saxlama üçün FileWriter-dən append rejimində ( true ötürürük) istifadə edirik, beləliklə köhnə qeydlər silinmir — hər yeni sətir faylın sonuna səliqə ilə əlavə olunur. BufferedWriter şəklində örtük yazmanı sürətli və səmərəli edir: məlumatlar əvvəlcə yaddaşda toplanır, sonra isə bir dəfəyə diskə yazılır.
Bundan sonra eyni faylı BufferedReader ilə oxumağa açırıq. O, məzmunu böyük bloklarla götürür, sizə isə sətirləri bir-bir verir. Dövrdə proqram sadəcə onları ekrana çıxarır və nəticədə bütün “günlüyünüzü” əvvəldən axıra görürsünüz.
3. BufferedReader və BufferedWriter-in üstünlükləri
Əhəmiyyətli sürət artımı
Buferləşdirilmiş axınlarla faylı oxuyanda və ya yazanda, proqram diskə onlarla, bəzən yüzlərlə dəfə az müraciət edir. Bu, xüsusilə böyük fayllarda nəzərə çarpır.
Rahat metodlar
- BufferedReader.readLine() — faylı sətir-sətir oxumağa imkan verir ki, bu da mətn fayllarını (məsələn, loglar, CSV, konfiqlər) emal etmək üçün çox rahatdır.
- BufferedWriter.newLine() — yeni sətir əlavə edir və OS-dən asılı olaraq düzgün simvolu qoyur.
İstifadə sadəliyi
- Siniflər digər axınlarla asanlıqla birləşdirilir (məsələn, müxtəlif kodlaşdırmalı faylları oxumaq üçün InputStreamReader-i BufferedReader-in içində istifadə etmək olar).
- try-with-resources istifadə edildikdə bütün resurslar avtomatik bağlanır.
Çeviklik
- Buferin ölçüsünü açıq şəkildə təyin etmək olar, əgər defolt (adətən 8 KB) sizə az və ya çox görünürsə:
BufferedReader reader = new BufferedReader(new FileReader("big.txt"), 16384); // 16 KB-lıq bufer
4. BufferedReader və BufferedWriter-dən nə vaxt istifadə etməli
Bunlardan istifadə edin:
- Mətn faylları ilə işləyirsinizsə (loglar, CSV, böyük mətn məlumatları).
- Faylı sətir-sətir oxumaq və ya yazmaq lazım olduqda.
- Böyük fayllarla işləyərkən sürət vacibdirsə.
- Şəbəkədən və ya Reader/Writer dəstəkləyən digər mənbədən gələn axını emal etməli olduqda.
Bunlardan istifadə ETMƏYİN:
- Binary fayllarla işləmək üçün (məsələn, şəkillər, arxivlər, video) — bunun üçün InputStream/OutputStream var.
- Fayl çox kiçikdirsə və bir dəfə tam oxunur/yazılırsa — bu halda buferləşdirmənin qazancı minimal olacaq (amma zərəri də olmayacaq).
5. Faydalı incəliklər
Müxtəlif kodlaşdırmalarla birləşdirmə
Əgər faylları müəyyən kodlaşdırmada oxumaq/yazmaq lazımdırsa (məsələn, "UTF-8", "Windows-1251"), buferləşdirilmiş axınlarla birlikdə InputStreamReader/OutputStreamWriter-dən istifadə edin:
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("input.txt"), "UTF-8")
);
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")
);
Buferin açıq şəkildə sıfırlanması
Bəzən məlumatın mütləq diskə düşməsi vacibdir (məsələn, log və ya qəbz yazanda). Bunun üçün writer.flush() çağırın. Adətən buna ehtiyac olmur, çünki axını bağlamaq buferi avtomatik sıfırlayır.
Buferin ölçüsü
Susmaya görə bufer təxminən 8 KB-dır. Əmin olduğunuz halda başqa ölçü təyin edin — məsələn, nəhəng faylları emal edərkən performans artımı verə bilər.
Müqayisə: FileReader/FileWriter vs BufferedReader/BufferedWriter
| Sinif | Böyük fayllarda sürət | Sətir-sətir oxuma rahatlığı | Sətir-sətir yazma rahatlığı | Kodlaşdırmalarla çeviklik |
|---|---|---|---|---|
| FileReader/FileWriter | Ləng | Xeyr (yalnız simvol-simvol) | Xeyr (yalnız simvol-simvol) | Yalnız standart |
| BufferedReader/Writer | Sürətli | Bəli (readLine()) | Bəli (newLine()) | Bəli ( InputStreamReader/OutputStreamWriter vasitəsilə) |
6. BufferedReader və BufferedWriter ilə işləyərkən tipik səhvlər
Səhv №1: Axını bağlamağı unutmaq. Əgər try-with-resources-dən istifadə etməsəniz və ya close() çağırmasanız, fayl bloklana bilər və məlumat diskə yazılmaya bilər. Həmişə try-with-resources istifadə edin!
Səhv №2: Mətn və binary fayllarla işi səhv salmaq. Binary faylı (".jpg", ".zip") BufferedReader ilə açmağa cəhd etmək “qarışıq simvollara” və çox güman ki, xətalara gətirib çıxaracaq. Binary fayllar üçün InputStream/OutputStream istifadə edin.
Səhv №3: Böyük həcmli məlumatlarda buferləşdirmədən istifadə etməmək. Əgər simvol-simvol oxusanız və ya yazsanız, proqram ləng işləyəcək. Böyük fayllar üçün həmişə buferləşdirmədən istifadə edin.
Səhv №4: Zəruri hallarda flush() çağırmamaq. Məlumatın dərhal diskdə görünməsi lazımdırsa (məsələn, jurnallaşdırma üçün), writer.flush() çağırın. Amma adətən axını bağlamaq bunu sizin yerinizə edir.
Səhv №5: Kodlaşdırmanı nəzərə almamaq. Faylı səhv kodlaşdırma ilə açsanız, mətn düzgün göstərilməyə bilər (hərflər “?”-a, anlaşılmaz işarələrə çevrilir və s.). Sistem kodlaşdırmasından fərqlənirsə, lazım olan kodlaşdırmanı həmişə açıq şəkildə göstərin (məsələn, "UTF-8").
GO TO FULL VERSION