CodeGym /Kurslar /JAVA 25 SELF /FileVisitor — fayl sisteminin gəzilməsi və rekursiv əməli...

FileVisitor — fayl sisteminin gəzilməsi və rekursiv əməliyyatlar

JAVA 25 SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

Gəlin Files.walk() ilə gəzinti nümunəsini yada salaq:

Path start = Paths.get("my-folder");
try (Stream<Path> stream = Files.walk(start)) {
    stream.forEach(System.out::println);
}

Bu, sadəcə fayl və qovluqlar üzrə gəzmək və onlarla nəsə etmək lazım olanda əla işləyir. Bəs tapşırıq daha mürəkkəbdirsə?

  • Qovluğu bütün faylları və alt qovluqları ilə birlikdə rekursiv şəkildə silmək lazımdır (yalnız boş olduqda deyil).
  • Qovluğu rekursiv şəkildə kopyalamaq və ya köçürmək lazımdır.
  • Statistikanı toplamaq lazımdır (məsələn, bütün faylların ümumi ölçüsünü hesablamaq, faylları genişlənmələrə görə qruplaşdırmaq).
  • Xətaları emal etmək lazımdır (məsələn, hər hansı qovluğa giriş yoxdursa, hər şeyin çökməsini istəmirik).

Belə hallarda Stream API artıq o qədər də rahat olmur: lambda-larda try-catch qurmalı olursunuz, keçidin ardıcıllığına nəzarət etməlisiniz (məsələn, əvvəl faylları, sonra qovluqları silmək üçün) və kod oxunmaz olur.

Məhz bu tapşırıqlar üçün fayl sistemi ağacını gəzən FileVisitor mexanizmi nəzərdə tutulub.

2. FileVisitor interfeysi: necə qurulub

FileVisitor<T> interfeysi — fayl sistemini gəzərkən ziyarət edilən hər bir fayl və qovluq barədə bildirişlər alan «hadisə emalçısıdır».

Siz Files.walkFileTree(start, visitor) çağırdıqda, Java göstərilən yoldan başlayaraq ağacı gəzməyə başlayır və hər mərhələdə sizin FileVisitor-ünüzün uyğun metodunu çağırır.

FileVisitor interfeysinin əsas metodları

İnterfeys belə görünür:

public interface FileVisitor<T> {
    FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException;
    FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException;
    FileVisitResult visitFileFailed(T file, IOException exc) throws IOException;
    FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException;
}
  • preVisitDirectory — qovluğa daxil olmadan əvvəl çağırılır.
  • visitFile — hər bir fayl üçün çağırılır.
  • visitFileFailed — fayla giriş alınmadıqda çağırılır.
  • postVisitDirectory — qovluqdan çıxandan sonra çağırılır (yəni onun bütün faylları və alt qovluqları emal edildikdən sonra).

Bu metodların hər biri keçidin necə davam edəcəyini müəyyən edən FileVisitResult tipində dəyər qaytarır:

  • FileVisitResult.CONTINUE — gəzintini davam etdirmək.
  • FileVisitResult.SKIP_SUBTREE — cari qovluğu və içində olan hər şeyi ötürmək.
  • FileVisitResult.SKIP_SIBLINGS — qalan «qardaşları» (bu səviyyədəki fayl və qovluqları) ötürmək.
  • FileVisitResult.TERMINATE — gəzintini tam dayandırmaq.

SimpleFileVisitor sinfi

Hər dəfə bütün dörd metodu reallaşdırmaq yorucudur, xüsusilə də yalnız bir-ikisinə ehtiyacınız varsa. Buna görə Java-da rahat bir adapter sinfi — SimpleFileVisitor var: o, bütün metodları «susmaya görə» davranışla (sadəcə CONTINUE) artıq reallaşdırır və siz yalnız gərəkli olanları yenidən təyin edə bilərsiniz.

Nümunə:

import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class MyVisitor extends SimpleFileVisitor<Path> {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        System.out.println("Fayl: " + file);
        return FileVisitResult.CONTINUE;
    }
}

3. Files.walkFileTree istifadə edilməsi: əsas nümunə

Nümunə 1: Bütün fayl və qovluqları çap edirik

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class TreePrinter {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("my-folder");

        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
                System.out.println("Qovluq: " + dir);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                System.out.println("  Fayl: " + file);
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

Çıxış:

Qovluq: my-folder
  Fayl: my-folder/file1.txt
  Fayl: my-folder/file2.txt
Qovluq: my-folder/subdir
  Fayl: my-folder/subdir/nested.txt

Gördüyünüz kimi, gəzinti «dərinlik üzrə» aparılır: əvvəl qovluğa daxil oluruq, sonra onun fayllarını emal edirik, sonra alt qovluqlara keçiririk.

4. Nümunə: qovluğun rekursiv silinməsi

Ən çox rast gəlinən tapşırıqlardan biri: qovluğu bütün məzmunu ilə silmək. Əgər Files.delete(path) ilə qovluğu silməyə cəhd etsəniz, o boş deyilsə — istisna alacaqsınız. Əvvəlcə bütün faylları və alt qovluqları silmək, sonra isə qovluğun özünü silmək lazımdır.

Bunu FileVisitor ilə belə etmək olar:

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class RecursiveDelete {
    public static void main(String[] args) throws IOException {
        Path dirToDelete = Paths.get("test-folder");

        Files.walkFileTree(dirToDelete, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.delete(file); // faylı silirik
                System.out.println("Fayl silindi: " + file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                Files.delete(dir); // içindəkiləri sildikdən sonra qovluğu silirik
                System.out.println("Qovluq silindi: " + dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

Vacib məqam:
Qovluqların silinməsi postVisitDirectory-də baş verir, yəni məzmun tam silindikdən sonra. Əgər qovluğu içindəki faylları silməzdən əvvəl silməyə çalışsaydıq — səhv alardıq.

5. Nümunə: kataloqdakı bütün faylların ümumi ölçüsünün hesablanması

Gəlin indi qovluqdakı və onun alt qovluqlarındakı bütün faylların ümumi ölçüsünü hesablayan bir FileVisitor yazaq.

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class DirectorySizeCalculator {
    private static long totalSize = 0;

    public static void main(String[] args) throws IOException {
        Path start = Paths.get("my-folder");

        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                totalSize += attrs.size();
                return FileVisitResult.CONTINUE;
            }
        });

        System.out.println("Ümumi ölçü: " + totalSize + " bayt");
    }
}

Diqqət yetirin:
Yığım üçün totalSize sahəsindən istifadə edirik. Real tətbiqlərdə statik sahələrdən qaçmaq və dəyişənləri obyektlər vasitəsilə ötürmək daha düzgündür, lakin sadəlik üçün bu nümunədə belədir.

6. Nümunə: FileVisitor ilə maska (genişlənmə) üzrə faylların axtarışı

Tutaq ki, kataloqda və alt qovluqlarda bütün .txt fayllarını tapmaq və siyahılarını çıxarmaq lazımdır.

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class TxtFileFinder {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("my-folder");

        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (file.getFileName().toString().endsWith(".txt")) {
                    System.out.println("Tapılan .txt faylı: " + file);
                }
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

Əgər tapılan faylların siyahısını toplamaq istəsəniz, bir siyahı yarada bilərsiniz:

import java.util.ArrayList;
import java.util.List;

// main daxilində:
List<Path> txtFiles = new ArrayList<>();
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        if (file.getFileName().toString().endsWith(".txt")) {
            txtFiles.add(file);
        }
        return FileVisitResult.CONTINUE;
    }
});
System.out.println("Cəmi tapıldı: " + txtFiles.size());

7. Xətaların emalı və gəzintinin xüsusiyyətləri

Fayla və ya qovluğa giriş yoxdursa nə etmək lazımdır?

Bəzən gəzinti zamanı giriş hüququ olmayan fayl və ya qovluqlara rast gəlirsiniz (məsələn, icazəniz yoxdur və ya fayl başqa proses tərəfindən istifadə olunur). Bu halda visitFileFailed metodu çağırılacaq.

Nümunə:

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
    System.err.println("Fayla giriş xətası: " + file + " (" + exc + ")");
    return FileVisitResult.CONTINUE; // xətaya baxmayaraq keçidi davam edirik
}

Əgər səhv baş verəndə gəzintini dayandırmaq istəyirsinizsə, TERMINATE qaytarın.

Qovluğu tamamilə necə ötürmək olar?

Müəyyən bir qovluğa (məsələn, .git və ya node_modules) girmək istəmirsinizsə, preVisitDirectory metodunda SKIP_SUBTREE qaytara bilərsiniz:

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
    if (dir.getFileName().toString().equals("node_modules")) {
        return FileVisitResult.SKIP_SUBTREE; // bu qovluğa və onun alt qovluqlarına daxil olmamaq
    }
    return FileVisitResult.CONTINUE;
}

8. Praktika: öz FileVisitor-unuzu reallaşdıraq

Gəlin aşağıdakıları edən bir FileVisitor həyata keçirək:

  • Kataloqda və alt qovluqlarda .java genişlənməsi olan bütün faylları tapır,
  • Onların sayını hesablayır,
  • Bu faylların ümumi ölçüsünü hesablayır.
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class JavaFilesStats {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("src"); // məsələn, layihənin mənbə kodu

        class JavaFileVisitor extends SimpleFileVisitor<Path> {
            int count = 0;
            long totalSize = 0;

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (file.getFileName().toString().endsWith(".java")) {
                    count++;
                    totalSize += attrs.size();
                    System.out.println("Tapılan .java faylı: " + file);
                }
                return FileVisitResult.CONTINUE;
            }
        }

        JavaFileVisitor visitor = new JavaFileVisitor();
        Files.walkFileTree(start, visitor);

        System.out.println("Cəmi .java faylı: " + visitor.count);
        System.out.println("Ümumi ölçü: " + visitor.totalSize + " bayt");
    }
}

9. FileVisitor istifadəsində tipik səhvlər

Səhv № 1: daxildəki faylları silməzdən əvvəl qovluğu silmək cəhdi. Əgər Files.delete(dir)-i preVisitDirectory-də çağırırsınızsa, istisna alacaqsınız — əvvəl bütün fayl və alt qovluqları silmək, yalnız ondan sonra qovluğun özünü silmək lazımdır (bunu postVisitDirectory-də edin).

Səhv № 2: giriş xətalarını emal etməmə. Əgər visitFileFailed-i yenidən təyin etməsəniz, proqram qorunan fayla rast gələndə gözlənilmədən dayana bilər. Xətanı açıq şəkildə çıxarmaq və gəzintini davam etdirmək daha yaxşıdır.

Səhv № 3: «faylı gizlətmək» əməliyyatının bütün OS-lərdə eyni işləyəcəyini güman etmək. Linux və macOS-da gizli fayl nöqtə ilə başlayan fayldır (.gitignore), Windows-da isə xüsusi atribut qoyulubsa gizli sayılır. Bu yanaşmaları qarışdırmayın.

Səhv № 4: nəticəni toplamaq üçün çoxaxınlı tətbiqlərdə statik sahələrdən istifadə etmək. Əgər bir neçə gəzintini paralel işə salırsınızsa, statik sahələr çaşqınlığa səbəb olacaq. Daha yaxşısı instans sahələrindən (və ya yuxarıdakı nümunədə olduğu kimi lokal sinifdən) istifadə etməkdir.

Səhv № 5: FileVisitor daxilində axınlarla işləyərkən resursları bağlamağı unutmaq. Sizin FileVisitor-unuz faylları oxuyursa/yazırsa, sızmaların qarşısını almaq üçün try-with-resources-dan istifadə edin.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Tədqiqatçının köməkçisi: layihə qeydlərinin toplanması
Tədqiqatçının köməkçisi: layihə qeydlərinin toplanması
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Rəqəmsal təmizləyici: layihənin geri dönməz silinməsi
Rəqəmsal təmizləyici: layihənin geri dönməz silinməsi
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION