1. NIO2: daha ətraflı tanışlıq
Artıq NIO2 ilə tanış olmuşuq, indi isə fayllar və qovluqlarla işləmək üçün bu faydalı kitabxana barədə biliklərimizi təkrar edib dərinləşdirəcəyik.
Əvvəllər Java-da yalnız File sinfi var idi. O, faylın mövcudluğunu yoxlamaq, fayl və qovluq yaratmaq və silmək, qovluqdakı faylların siyahısını almaq kimi işləri bacarırdı. Amma çox məhdudiyyətləri vardı:
- yollarla işləmək əlverişli deyildi, xüsusən də müxtəlif əməliyyat sistemlərini nəzərə almaq lazım olanda (C:\Users\user\file.txt Windows-da və /home/user/file.txt Linux-da);
- simvolik keçidlər, giriş hüquqları və fayl atributları üçün normal dəstək yox idi;
- qovluq ağacının gəzilməsi imkanları məhdud idi;
- xətaların emalı kifayət qədər zəif idi.
Java 7-də NIO2 (New Input/Output, versiya 2) meydana çıxanda, tərtibatçıların işi daha asan və rahat oldu. İndi bizim ixtiyarımızda bunlar var:
- fayl və qovluq yolları ilə rahat işləmək üçün Path sinfi;
- bütün əsas əməliyyatları təmin edən Files sinfi: oxuma, yazma, kopyalama, silmə, fayllar barədə məlumatların əldə edilməsi;
- FileVisitor interfeysi və Files.walk kimi metodlar ki, fayl sistemini asan və çevik şəkildə gəzməyə imkan verir.
Niyə bu vacibdir?
- Çoxplatformalılıq: Eyni kod Windows, Linux, macOS üzərində işləyir, ayırıcılara (/ və ya \) görə narahat olmadan.
- Təhlükəsizlik və rahatlıq: Xətalar barədə daha çox informasiya, daha az “sehr” və gözlənilməz sürprizlər.
- Güclü imkanlar: Nəhəng qovluqları emal etmək, hətta filtrləmə və paralel emalla rekursiv gəzinti etmək mümkündür.
2. Əsas siniflər: Path və Files
Sinif Path
Path — fayl və ya qovluğa aparan yolun müasir təqdimatıdır. O, mütləq real mövcud fayla işarə etmir — sadəcə üzərində rahat işləmək üçün bir yoldur.
Path əldə etmək
import java.nio.file.Path;
import java.nio.file.Paths;
Path path1 = Paths.get("file.txt"); // nisbi yol
Path path2 = Paths.get("/home/user/file.txt"); // mütləq yol
Path path3 = Path.of("mydir", "subdir", "file.txt"); // Java 11+ ilə
Fakt: Path əməliyyat sistemindən asılı deyil. / və ya \ ilə sətirləri əl ilə birləşdirməyi unudun!
Sətirə çevirmək
System.out.println(path1.toString());
Valideyn qovluğu və fayl adını almaq
Path parent = path1.getParent(); // nisbi yollar üçün null ola bilər
Path fileName = path1.getFileName(); // yalnız fayl adı
Sinif Files
Files — fayl və qovluqlarla bağlı bütün əməliyyatlar üçün statik metodların toplusudur:
- Mövcudluğu yoxlama: Files.exists(path)
- Faylların oxunması və yazılması: Files.readAllBytes(path), Files.write(path, bytes)
- Məlumatların əldə edilməsi: Files.size(path), Files.getLastModifiedTime(path)
- Kopyalama, silmə, köçürmə: Files.copy, Files.delete, Files.move
Nümunələr:
import java.nio.file.Files;
import java.nio.file.Path;
Path path = Path.of("file.txt");
if (Files.exists(path)) {
System.out.println("Fayl mövcuddur!");
System.out.println("Ölçü: " + Files.size(path) + " bayt");
System.out.println("Son dəyişiklik: " + Files.getLastModifiedTime(path));
} else {
System.out.println("Fayl tapılmadı.");
}
3. Fayl sisteminin gəzilməsi: Files.walk və “dostları”
Köhnə yanaşmanın problemi
Köhnə API-də kataloq və onun alt qovluqlarındakı bütün faylları gəzmək üçün rekursiv funksiyalar yazmaq, fayl ilə qovluğu əl ilə ayırd etmək və sonsuz rekursiyaya düşməmək üçün diqqətli olmaq lazım idi. Bu, təkcə yorucu deyildi, həm də səhv etmək çox asan idi.
Müasir üsul: Files.walk
Files.walk(Path start) Stream<Path> qaytarır — göstərilən yoldan başlayaraq bütün alt qovluqlar daxil olmaqla, fayl və qovluqların hamısının axını. İndi fayl sistemini gəzmək — bu sadəcə Axınlarla (Streams) işləməkdir!
Nümunə: Bütün faylları və qovluqları çıxarmaq
import java.nio.file.*;
try (var paths = Files.walk(Path.of("mydir"))) {
paths.forEach(System.out::println);
}
Burada mydir qovluğundan başlayaraq bütün yollar (həm fayllar, həm də qovluqlar) çıxarılacaq.
Nümunə: Yalnız fayllar (qovluqlar olmadan)
try (var paths = Files.walk(Path.of("mydir"))) {
paths.filter(Files::isRegularFile)
.forEach(System.out::println);
}
Files.isRegularFile(path) metodu yalnız adi fayllar üçün true qaytarır (qovluq və ya simvolik keçid deyil).
Nümunə: Faylları uzantıya görə axtarmaq
Tutaq ki, kataloq və alt qovluqlarda bütün .txt fayllarını tapmaq lazımdır:
try (var paths = Files.walk(Path.of("mydir"))) {
paths.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".txt"))
.forEach(System.out::println);
}
Nümunə: Bütün faylların ümumi ölçüsünü hesablayın
long totalSize = 0;
try (var paths = Files.walk(Path.of("mydir"))) {
totalSize = paths.filter(Files::isRegularFile)
.mapToLong(path -> {
try {
return Files.size(path);
} catch (Exception e) {
System.err.println("Ölçünü oxuma xətası: " + path);
return 0L;
}
})
.sum();
}
System.out.println("Faylların ümumi ölçüsü: " + totalSize + " bayt");
Vacibdir!
- Files.walk metodu bağlanmalı olan bir axın qaytarır (AutoCloseable reallaşdırır). Buna görə də try-with-resources istifadə edirik!
- Susmaya görə gəzinti dərinliyi ən dərin səviyyəyədəkdir (bütün alt qovluqlar). Dərinliyi məhdudlaşdıra bilərsiniz: Files.walk(path, maxDepth)
4. Praktiki tapşırıqlar
Tapşırıq 1: Kataloqda bütün şəkilləri tapın
.jpg, .png, .gif uzantılı bütün faylları images qovluğunda tapıb adlarını çıxarmaq lazımdır.
import java.nio.file.*;
import java.util.Set;
Set<String> extensions = Set.of(".jpg", ".png", ".gif");
try (var paths = Files.walk(Path.of("images"))) {
paths.filter(Files::isRegularFile)
.filter(path -> {
String name = path.getFileName().toString().toLowerCase();
return extensions.stream().anyMatch(name::endsWith);
})
.forEach(System.out::println);
}
Tapşırıq 2: Bütün .txt fayllarını başqa qovluğa kopyalayın
import java.nio.file.*;
Path sourceDir = Path.of("src");
Path destDir = Path.of("dest");
try (var paths = Files.walk(sourceDir)) {
paths.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".txt"))
.forEach(path -> {
try {
Path relative = sourceDir.relativize(path);
Path target = destDir.resolve(relative);
Files.createDirectories(target.getParent());
Files.copy(path, target, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Kopyalandı: " + path + " -> " + target);
} catch (Exception e) {
System.err.println("Kopyalama xətası: " + path);
}
});
}
Burada alt qovluqların strukturu saxlanılır.
5. Faydalı nüanslar
NIO2 üstünlükləri
Çoxplatformalılıq
Path qovluq ayırıcılardan özü baş çıxarır. Kodunuz Windows, Linux, macOS üzərində eyni cür işləyəcək.
Axınlarla emal
Files.walk kimi metodlar (Stream<Path>) qaytarır; bu axını filtrasiya edə, çevirdə, kolleksiyalara toplaya bilərsiniz — Stream API nə bacarırsa.
Böyük qovluqlarla işləmək
Köhnə API fayl sayı çox olanda (məsələn, 100 000 şəkil) çökə bilərdi. NIO2 belə halları asanlıqla emal edir, çünki hər şeyi dərhal yaddaşa yükləmir.
Simvolik keçidlər, atributlar, giriş hüquqları
Yolun simvolik keçid olub-olmadığını öyrənmək (Files.isSymbolicLink(path)), POSIX icazələrini almaq (Files.getPosixFilePermissions(path)), fayl sahibini bilmək və daha çoxunu etmək mümkündür.
Köhnə və yeni API-nin müqayisəsi
| Əməliyyat | Köhnə API (File) | Yeni API (Path, Files) |
|---|---|---|
| Mövcudluğu yoxlamaq | |
|
| Ölçünü almaq | |
|
| Qovluqdakı faylların siyahısı | |
|
| Rekursiv gəzinti | Rekursiya əl ilə | |
| Faylın kopyalanması | file.renameTo() (etibarsız) | |
| Uzantını almaq | Sətiri parse etmək | |
| Valideyn qovluğunu almaq | |
|
| Hüquqlarla iş | Demək olar ki, yox | |
Vacib xüsusiyyətlər
Fayl tipinin yoxlanması
- Files.isRegularFile(path) — adi fayl
- Files.isDirectory(path) — qovluq
- Files.isSymbolicLink(path) — simvolik keçid
Böyük qovluqlarla iş
- Bütün yolları siyahıda toplamaq lazım deyil: Stream<Path> ilə işləyin və gələn kimi emal edin.
- İş bitdikdən sonra axın mütləq bağlanmalıdır (try-with-resources).
İstisnalar
- Demək olar ki, bütün metodlar IOException ata bilər — xətaları emal etməyi (və ya yuxarı atmağı) unutmayın.
Gəzintinin dərinliyinin məhdudlaşdırılması
try (var paths = Files.walk(Path.of("mydir"), 2)) { // cəmi 2 səviyyə
// ...
}
6. NIO2 ilə işləyərkən tipik səhvlər
Səhv №1: Files.walk axınını bağlamağı unutmaq. Əgər try-with-resources istifadə etməsəniz, resurs sızması baş verə bilər — fayl sistemi axını açıq qalacaq. Həmişə try (var paths = Files.walk(...)) { ... } konstruktsiyasından istifadə edin.
Səhv №2: yolun qovluq olub-olmadığını yoxlamamaq. Files.walk metoduna qovluq əvəzinə fayl yolu versəniz, gözlənilməz davranış və ya xəta ala bilərsiniz.
Səhv №3: istisnaları emal etməmək. Demək olar ki, bütün NIO2 metodları IOException ata bilər. Bu xətaları diqqətsiz qoymayın — ən azı istifadəçiyə mesaj göstərin və ya jurnallaşdırın.
Səhv №4: yol ayırıcları ilə qarışıqlıq. Əgər yolları əl ilə / və ya \ vasitəsilə birləşdirirsinizsə, bunu etməyə dəyməz! Path.of(...) və ya resolve(...) istifadə edin — onlar özləri doğru şəkildə həll edəcəklər.
Səhv №5: çox böyük qovluğu “yadaşa” yığmağa cəhd. Fayl sayı çox olanda bütün yolları siyahıda toplamayın — Stream<Path> ilə işləyin və onları ardıcıl emal edin.
Səhv №6: çoxplatformalılığı unutmaq. Windows və ya Unix tərzində absolyut yolları hardkod etməyin. Path və resolve/relativize kimi əməliyyatlardan istifadə edin — onlar istənilən OS-də düzgün işləyəcək.
GO TO FULL VERSION