Wyświetl rekursywnie wszystkie pliki z katalogu w Javie


85

Mam tę funkcję, która rekurencyjnie wypisuje nazwy wszystkich plików w katalogu. Problem polega na tym, że mój kod jest bardzo wolny, ponieważ musi uzyskiwać dostęp do zdalnego urządzenia sieciowego w każdej iteracji.

Mój plan polega na tym, aby najpierw załadować wszystkie pliki z katalogu rekurencyjnie, a następnie przejrzeć wszystkie pliki za pomocą wyrażenia regularnego, aby odfiltrować wszystkie pliki, których nie chcę. Czy ktoś ma lepszą sugestię?

public static printFnames(String sDir){
  File[] faFiles = new File(sDir).listFiles();
  for(File file: faFiles){
    if(file.getName().matches("^(.*?)")){
      System.out.println(file.getAbsolutePath());
    }
    if(file.isDirectory()){
      printFnames(file.getAbsolutePath());
    }
  }
}

To tylko test później. Nie zamierzam używać takiego kodu, zamiast tego dodam ścieżkę i datę modyfikacji każdego pliku, który pasuje do zaawansowanego wyrażenia regularnego do tablicy.


1
... Jakie jest pytanie? Czy szukasz tylko potwierdzenia, że ​​ten kod będzie działał?
Richard JP Le Guen

Nie, wiem, że ten kod działa, ale jest bardzo powolny i wydaje się, że dostęp do systemu plików i pobieranie zawartości dla każdego podkatalogu jest głupi, zamiast pobierać wszystko na raz.
Hultner

Odpowiedzi:


134

Zakładając, że jest to rzeczywisty kod produkcyjny będziesz pisać, to proponuję przy użyciu rozwiązanie do tego typu rzeczy, które zostały już rozwiązane - Apache Commons IO , a konkretnie FileUtils.listFiles(). Obsługuje zagnieżdżone katalogi, filtry (na podstawie nazwy, czasu modyfikacji itp.).

Na przykład dla Twojego wyrażenia regularnego:

Collection files = FileUtils.listFiles(
  dir, 
  new RegexFileFilter("^(.*?)"), 
  DirectoryFileFilter.DIRECTORY
);

Spowoduje to rekurencyjne wyszukiwanie plików pasujących do ^(.*?)wyrażenia regularnego, zwracając wyniki jako kolekcję.

Warto zauważyć, że nie będzie to szybsze niż toczenie własnego kodu, robi to samo - przeszukiwanie systemu plików w Javie jest po prostu powolne. Różnica polega na tym, że wersja Apache Commons nie będzie zawierała żadnych błędów.


Szukałem tam i stamtąd używałbym commons.apache.org/io/api-release/index.html?org/apache/commons/…, aby pobrać cały plik z katalogu i podkatalogów, a następnie przeszukać pliki, aby pasują do mojego wyrażenia regularnego. A może się mylę?
Hultner

Tak, problem, skanowanie folderu zajmuje ponad godzinę i robienie tego za każdym razem, gdy uruchamiam program w celu sprawdzenia aktualizacji, jest niezwykle irytujące. Czy byłoby szybciej, gdybym napisał tę część programu w C, a resztę w Javie, a jeśli tak, to czy byłaby to jakaś znacząca różnica? Na razie zmieniłem kod w wierszu if isdir i dodałem, aby katalog również pasował do wyrażenia regularnego, aby został uwzględniony w wyszukiwaniu. Widzę, że w twoim przykładzie jest napisane DirectoryFileFilter.DIRECTORY, myślę, że mógłbym tam mieć filtr regex.
Hultner

1
napisanie go przy użyciu natywnych wywołań absolutnie przyspieszyłoby to - FindFirstFile / FineNextFile pozwala na sprawdzanie atrybutów pliku bez konieczności wykonywania oddzielnego wywołania - może to mieć ogromne konsekwencje dla sieci o większych opóźnieniach. Podejście Javy do tego jest strasznie nieefektywne.
Dzień Kevina

5
@ hanzallah-afgan: Zarówno pytanie, jak i odpowiedź mają ponad 5 lat. W tamtym czasie pojawiły się dwie główne wersje Java, więc możesz nie chcieć badać nowszych funkcji, takich jak Java 7 NIO.
Hultner

4
Używaj FileUtils tylko wtedy, gdy znasz i akceptujesz uderzenie wydajnościowe: github.com/brettryan/io-recurse-tests . Natywne alternatywy java8 pozwalają na zwięzłą i wydajniejszą notację, np .:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza

64

W Java 8, to jest 1-liniowej przez Files.find()z dowolnie dużą głębokość (na przykład 999), a BasicFileAttributeszisRegularFile()

public static printFnames(String sDir) {
    Files.find(Paths.get(sDir), 999, (p, bfa) -> bfa.isRegularFile()).forEach(System.out::println);
}

Aby dodać więcej filtrowania, popraw lambdę, na przykład wszystkie pliki jpg zmodyfikowane w ciągu ostatnich 24 godzin:

(p, bfa) -> bfa.isRegularFile()
  && p.getFileName().toString().matches(".*\\.jpg")
  && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000

3
Sugeruję, aby zawsze używać tych metod Files, które zwracają Stream w blokach try-with-resources: w przeciwnym razie zasób pozostanie otwarty
riccardo.tasso

Czy operacje terminalowe nie wywołują zamykania samego strumienia?
Dragas

@Dragas yes. Mój konsument to tylko prosty przykład; w prawdziwym życiu zrobiłbyś coś bardziej pożytecznego.
Bohemian

27

Jest to bardzo prosta rekurencyjna metoda pobierania wszystkich plików z podanego katalogu głównego.

Wykorzystuje klasę Java 7 NIO Path.

private List<String> getFileNames(List<String> fileNames, Path dir) {
    try(DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
        for (Path path : stream) {
            if(path.toFile().isDirectory()) {
                getFileNames(fileNames, path);
            } else {
                fileNames.add(path.toAbsolutePath().toString());
                System.out.println(path.getFileName());
            }
        }
    } catch(IOException e) {
        e.printStackTrace();
    }
    return fileNames;
} 

18

Wraz z Javą 7 wprowadzono szybszy sposób poruszania się po drzewie katalogów z funkcjonalnością Pathsi Files. Są znacznie szybsze niż „stary” Filesposób.

Byłby to kod do przechodzenia i sprawdzania nazw ścieżek za pomocą wyrażenia regularnego:

public final void test() throws IOException, InterruptedException {
    final Path rootDir = Paths.get("path to your directory where the walk starts");

    // Walk thru mainDir directory
    Files.walkFileTree(rootDir, new FileVisitor<Path>() {
        // First (minor) speed up. Compile regular expression pattern only one time.
        private Pattern pattern = Pattern.compile("^(.*?)");

        @Override
        public FileVisitResult preVisitDirectory(Path path,
                BasicFileAttributes atts) throws IOException {

            boolean matches = pattern.matcher(path.toString()).matches();

            // TODO: Put here your business logic when matches equals true/false

            return (matches)? FileVisitResult.CONTINUE:FileVisitResult.SKIP_SUBTREE;
        }

        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
                throws IOException {

            boolean matches = pattern.matcher(path.toString()).matches();

            // TODO: Put here your business logic when matches equals true/false

            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path path,
                IOException exc) throws IOException {
            // TODO Auto-generated method stub
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path path, IOException exc)
                throws IOException {
            exc.printStackTrace();

            // If the root directory has failed it makes no sense to continue
            return path.equals(rootDir)? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
        }
    });
}

5
Dobra odpowiedź :), jest też jego zaimplementowana klasa o nazwie "SimpleFileVisitor", jeśli nie potrzebujesz wszystkich zaimplementowanych funkcji, możesz po prostu nadpisać potrzebne funkcje.
GalDude33

13

Szybki sposób na pobranie zawartości katalogu za pomocą Java 7 NIO:

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.Path;

...

Path dir = FileSystems.getDefault().getPath( filePath );
DirectoryStream<Path> stream = Files.newDirectoryStream( dir );
for (Path path : stream) {
   System.out.println( path.getFileName() );
}
stream.close();

3
Ładnie, ale pobiera pliki tylko dla jednego katalogu. Jeśli chcesz zobaczyć wszystkie podkatalogi, zobacz moją alternatywną odpowiedź.
Dan

3
Files.newDirectoryStreammoże zgłosić IOException. Sugeruję zawinięcie tego wiersza w instrukcję try-with-with Java7, aby strumień był zawsze zamknięty dla Ciebie (wyjątek lub nie, bez potrzeby a finally). Zobacz także tutaj: stackoverflow.com/questions/17739362/ ...
Greg

12

Interfejs Java do odczytywania zawartości folderów systemu plików nie jest zbyt wydajny (jak odkryłeś). JDK 7 rozwiązuje ten problem dzięki zupełnie nowemu interfejsowi do tego typu rzeczy, który powinien zapewnić wydajność na poziomie natywnym w tego rodzaju operacjach.

Podstawowym problemem jest to, że Java tworzy natywne wywołanie systemowe dla każdego pojedynczego pliku. W przypadku interfejsu o niskim opóźnieniu nie jest to aż tak wielka sprawa - ale w sieci z nawet umiarkowanym opóźnieniem naprawdę się sumuje. Jeśli sprofilujesz swój algorytm powyżej, przekonasz się, że większość czasu spędzasz na nieznośnym wywołaniu isDirectory () - to dlatego, że ponosisz podróż w obie strony dla każdego pojedynczego wywołania isDirectory (). Większość nowoczesnych systemów operacyjnych może dostarczać tego rodzaju informacji, gdy pierwotnie zażądano listy plików / folderów (w przeciwieństwie do odpytywania każdej ścieżki pliku o jej właściwości).

Jeśli nie możesz czekać na JDK7, jedną ze strategii rozwiązania tego opóźnienia jest przejście na wiele wątków i użycie usługi ExecutorService z maksymalną liczbą wątków do wykonania rekursji. To nie jest świetne (musisz radzić sobie z blokowaniem struktur danych wyjściowych), ale będzie o wiele szybsze niż wykonanie tego pojedynczego wątku.

We wszystkich dyskusjach na ten temat zdecydowanie zalecam porównanie z najlepszymi, jakie można zrobić, używając kodu natywnego (lub nawet skryptu wiersza poleceń, który robi mniej więcej to samo). Mówienie, że przemierzanie struktury sieci zajmuje godzinę, wcale nie znaczy tak wiele. Poinformowanie nas, że możesz to zrobić natywnie w 7 sekund, ale w Javie zajmuje to godzinę, przyciągnie uwagę ludzi.


3
Java 7 jest teraz dostępna, więc przykład, jak to zrobić w Javie 7 byłby pomocny. Albo przynajmniej link. Lub nazwa zajęć do wyszukania w Google. - to w końcu «stackoverflow», a nie «teoretyczne cs» ;-).
Martin

3
Cóż, zobaczmy ... Mój oryginalny post był w marcu 2010 ... Jest teraz w styczniu 2012 ... I właśnie sprawdziłem historię zapasów sprzętu i nie widzę siebie w posiadaniu wehikułu czasu w marcu '10, więc myślę, że prawdopodobnie mam prawo odpowiadać bez podania wyraźnego przykładu ;-)
Kevin Day


7

to będzie działać dobrze ... i będzie rekurencyjne

File root = new File("ROOT PATH");
for ( File file : root.listFiles())
{
    getFilesRecursive(file);
}


private static void getFilesRecursive(File pFile)
{
    for(File files : pFile.listFiles())
    {
        if(files.isDirectory())
        {
            getFilesRecursive(files);
        }
        else
        {
            // do your thing 
            // you can either save in HashMap and use it as
            // per your requirement
        }
    }
}

1
Dobra odpowiedź, jeśli chcesz czegoś, co działa z java <7.
ssimm

3

Osobiście podoba mi się ta wersja FileUtils. Oto przykład, który wyszukuje wszystkie pliki mp3 lub flac w katalogu lub dowolnym z jego podkatalogów:

String[] types = {"mp3", "flac"};
Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true);

3

To zadziała dobrze

public void displayAll(File path){      
    if(path.isFile()){
        System.out.println(path.getName());
    }else{
        System.out.println(path.getName());         
        File files[] = path.listFiles();
        for(File dirOrFile: files){
            displayAll(dirOrFile);
        }
    }
}


Witamy w StackOverflow Mam's, czy możesz wyjaśnić, w jaki sposób Twoja odpowiedź jest ulepszeniem lub alternatywą dla wielu istniejących odpowiedzi?
Lilienthal

1

Ta funkcja prawdopodobnie wyświetli całą nazwę pliku i jego ścieżkę z jego katalogu i jego podkatalogów.

public void listFile(String pathname) {
    File f = new File(pathname);
    File[] listfiles = f.listFiles();
    for (int i = 0; i < listfiles.length; i++) {
        if (listfiles[i].isDirectory()) {
            File[] internalFile = listfiles[i].listFiles();
            for (int j = 0; j < internalFile.length; j++) {
                System.out.println(internalFile[j]);
                if (internalFile[j].isDirectory()) {
                    String name = internalFile[j].getAbsolutePath();
                    listFile(name);
                }

            }
        } else {
            System.out.println(listfiles[i]);
        }

    }

}

1
W tym przykładzie nie uwzględniono faktu, że metoda listFiles () może i zwróci wartość null. docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles ()
Matt Jones

1

Java 8

public static void main(String[] args) throws IOException {

        Path start = Paths.get("C:\\data\\");
        try (Stream<Path> stream = Files.walk(start, Integer.MAX_VALUE)) {
            List<String> collect = stream
                .map(String::valueOf)
                .sorted()
                .collect(Collectors.toList());

            collect.forEach(System.out::println);
        }


    }

0

wydaje się, że dostęp do systemu plików i pobieranie zawartości każdego podkatalogu jest głupi, zamiast pobierać wszystko na raz.

Twoje przeczucie jest złe. Tak działają systemy plików. Nie ma szybszego sposobu (z wyjątkiem sytuacji, gdy musisz to robić wielokrotnie lub dla różnych wzorców, możesz buforować wszystkie ścieżki plików w pamięci, ale wtedy musisz poradzić sobie z unieważnieniem pamięci podręcznej, tj. Co się dzieje, gdy pliki są dodawane / usuwane / zmieniane podczas aplikacja działa).


Chodzi o to, że chcę załadować wszystkie pliki określonego typu z określonym formatem nazwy do biblioteki, która jest prezentowana użytkownikowi i za każdym razem, gdy aplikacja jest uruchamiana, biblioteka ma zostać zaktualizowana, ale aktualizacja biblioteki zajmuje wieczność. Jedynym rozwiązaniem, które otrzymałem, jest uruchomienie aktualizacji w tle, ale nadal jest irytujące, że ładowanie wszystkich nowych plików trwa tak długo. Musi być na to lepszy sposób. Lub przynajmniej lepszy sposób na aktualizację bazy danych. To głupie, że przegląda wszystkie pliki, które już przeszedł. Czy istnieje sposób, aby szybko znaleźć aktualizacje?
Hultner

@Hultner: Java 7 będzie zawierała funkcję powiadamiania o aktualizacjach systemu plików, ale będzie to działać tylko wtedy, gdy aplikacja jest uruchomiona, więc jeśli nie chcesz, aby usługa w tle działała przez cały czas, nie pomoże. Mogą wystąpić specjalne problemy z udziałami sieciowymi, jak opisuje Kevin, ale dopóki polegasz na skanowaniu całego drzewa katalogów, naprawdę nie ma lepszego sposobu.
Michael Borgwardt

Być może mógłbyś stworzyć jakieś pliki indeksowe. Jeśli istnieje sposób na sprawdzenie rozmiaru katalogu, możesz po prostu przeskanować w poszukiwaniu nowych plików, gdy rozmiar się zmieni.
James P.

@James: nie ma możliwości sprawdzenia rozmiaru katalogu. Rozmiar katalogu uzyskuje się poprzez pobranie rozmiaru każdego pliku i zsumowanie go we wszystkich znanych mi systemach plików. Właściwie pytanie „jaki jest rozmiar tego katalogu?” nawet niekoniecznie ma sens, jeśli weźmiesz pod uwagę twarde linki.
Michael Borgwardt,

Masz rację. Nadal uważam, że buforowanie i / lub pobieranie odcisków palców może przyspieszyć ten proces.
James P.

0

Po prostu wiesz, że isDirectory () jest dość powolną metodą. W mojej przeglądarce plików jest to dość powolne. Będę szukał biblioteki, aby zastąpić ją kodem natywnym.


0

Bardziej wydajnym sposobem radzenia sobie z milionami folderów i plików jest przechwycenie listy katalogów za pomocą polecenia DOS w jakimś pliku i przeanalizowanie go. Po przeanalizowaniu danych możesz przeprowadzić analizę i obliczyć statystyki.


0
import java.io.*;

public class MultiFolderReading {

public void checkNoOfFiles (String filename) throws IOException {

    File dir=new File(filename);
    File files[]=dir.listFiles();//files array stores the list of files

 for(int i=0;i<files.length;i++)
    {
        if(files[i].isFile()) //check whether files[i] is file or directory
        {
            System.out.println("File::"+files[i].getName());
            System.out.println();

        }
        else if(files[i].isDirectory())
        {
            System.out.println("Directory::"+files[i].getName());
            System.out.println();
            checkNoOfFiles(files[i].getAbsolutePath());
        }
    }
}

public static void main(String[] args) throws IOException {

    MultiFolderReading mf=new MultiFolderReading();
    String str="E:\\file"; 
    mf.checkNoOfFiles(str);
   }
}

Proszę również o wyjaśnienie.
d4Rk

0

W Guava nie musisz czekać na zwrot kolekcji, ale możesz iterować po plikach. Łatwo sobie wyobrazić IDoSomethingWithThisFileinterfejs w podpisie poniższej funkcji:

public static void collectFilesInDir(File dir) {
    TreeTraverser<File> traverser = Files.fileTreeTraverser();
    FluentIterable<File> filesInPostOrder = traverser.preOrderTraversal(dir);
    for (File f: filesInPostOrder)
        System.out.printf("File: %s\n", f.getPath());
}

TreeTraverser pozwala również na przełączanie się między różnymi stylami przechodzenia.


0
public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        for (File fObj : dir.listFiles()) {
            if(fObj.isDirectory()) {
                ls.add(String.valueOf(fObj));
                ls.addAll(getFilesRecursively(fObj));               
            } else {
                ls.add(String.valueOf(fObj));       
            }
        }

        return ls;
    }
    public static List <String> getListOfFiles(String fullPathDir) {
        List <String> ls = new ArrayList<String> ();
        File f = new File(fullPathDir);
        if (f.exists()) {
            if(f.isDirectory()) {
                ls.add(String.valueOf(f));
                ls.addAll(getFilesRecursively(f));
            }
        } else {
            ls.add(fullPathDir);
        }
        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getListOfFiles("/Users/srinivasab/Documents");
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

0

Kolejny zoptymalizowany kod

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        if (dir.isDirectory())
            for (File fObj : dir.listFiles()) {
                if(fObj.isDirectory()) {
                    ls.add(String.valueOf(fObj));
                    ls.addAll(getFilesRecursively(fObj));               
                } else {
                    ls.add(String.valueOf(fObj));       
                }
            }
        else
            ls.add(String.valueOf(dir));

        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getFilesRecursively(new File("/Users/srinivasab/Documents"));
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

Czy możesz rozszerzyć swoją odpowiedź o bardziej szczegółowe wyjaśnienie? Będzie to bardzo przydatne do zrozumienia. Dziękuję Ci!
vezunchik

0

Jeszcze jeden przykład wyświetlania plików i katalogów przy użyciu języka Java 8 filter

public static void main(String[] args) {

System.out.println("Files!!");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isRegularFile)
                    .filter(c ->
                            c.getFileName().toString().substring(c.getFileName().toString().length()-4).contains(".jpg")
                            ||
                            c.getFileName().toString().substring(c.getFileName().toString().length()-5).contains(".jpeg")
                    )
                    .forEach(System.out::println);

        } catch (IOException e) {
        System.out.println("No jpeg or jpg files");
        }

        System.out.println("\nDirectories!!\n");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isDirectory)
                    .forEach(System.out::println);

        } catch (IOException e) {
            System.out.println("No Jpeg files");
        }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.