Rekurencyjnie wyświetlaj listę plików w Javie

258

Jak rekurencyjnie wyświetlać wszystkie pliki w katalogu w Javie? Czy środowisko udostępnia jakieś narzędzie?

Widziałem wiele nieuczciwych implementacji. Ale żaden z frameworka lub nio

Quintin Par
źródło
2
Właśnie ukończyłem Wyniki testu, które zapewniają testy wydajności dla wielu odpowiedzi. Nic dziwnego, że wszystkie odpowiedzi oparte na NIO działają najlepiej. Odpowiedź commons-io jest wyraźnie najgorsza z ponad dwukrotną długością przebiegu.
Brett Ryan,
2
Java8: Files.walk?
Benj

Odpowiedzi:

327

Java 8 zapewnia przyjemny strumień do przetwarzania wszystkich plików w drzewie.

Files.walk(Paths.get(path))
        .filter(Files::isRegularFile)
        .forEach(System.out::println);

Zapewnia to naturalny sposób przeglądania plików. Ponieważ jest to strumień, możesz wykonywać wszystkie fajne operacje na strumieniu, takie jak limit, grupowanie, mapowanie, wcześniejsze wyjście itp.

AKTUALIZACJA : Mogę wskazać, że istnieje także plik Files.find, który pobiera BiPredicate, który może być bardziej wydajny, jeśli chcesz sprawdzić atrybuty pliku.

Files.find(Paths.get(path),
           Integer.MAX_VALUE,
           (filePath, fileAttr) -> fileAttr.isRegularFile())
        .forEach(System.out::println);

Zauważ, że chociaż JavaDoc wymyka się temu, że ta metoda może być bardziej wydajna niż Files.walk , jest ona faktycznie identyczna, różnicę w wydajności można zaobserwować, jeśli również pobierasz atrybuty plików w swoim filtrze. Na koniec, jeśli chcesz filtrować według atrybutów, użyj Files.find , w przeciwnym razie użyj Files.walk , głównie dlatego, że występują przeciążenia i jest to wygodniejsze.

TESTY : Zgodnie z życzeniem przedstawiłem porównanie wydajności wielu odpowiedzi. Sprawdź projekt Github, który zawiera wyniki i przypadek testowy .

Brett Ryan
źródło
6
Jeden z tych przykładów, który może pokazać magię programowania funkcjonalnego nawet dla początkujących.
Johnny
2
Jak wypada to w porównaniu z metodami sprzed java 8? Moje bieżące przechodzenie do katalogu jest zbyt wolne i szukam czegoś, co je przyspieszy.
Sridhar Sarnobat
1
Piszę kilka testów zawierających większość wariantów w podanych odpowiedziach. Jak dotąd wydaje się, że najlepszym rozwiązaniem jest użycie Files.walkstrumienia równoległego, a tuż za nim Files.walkFileTree- tylko nieznacznie wolniej. Akceptowana odpowiedź przy użyciu commons-io jest zdecydowanie najwolniejsza w moich testach i jest 4 razy wolniejsza.
Brett Ryan
1
@BrettRyan, wypróbowałem twoje rozwiązanie, ale dostaję wyjątek Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException. Jak mogę to naprawić
Kachna
5
Jak uzyskać z tego rzeczywistą listę plików?
thouliha
159

FileUtils mają iterateFilesi listFilesmetody. Wypróbuj je. (z commons-io )

Edycja: tutaj możesz sprawdzić wyniki różnych podejść. Wydaje się, że podejście commons-IO jest powolny, więc wybrać tylko niektóre z nich szybciej stąd (jeśli ma to znaczenie)

Bozho
źródło
40
FYI / TLDR: jeśli chcesz po prostu rekursywnie wyświetlić wszystkie pliki bez filtrowania, zrób FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE), gdzie dirjest obiekt File wskazujący katalog podstawowy.
andronikus
2
Możesz rozważyć użycie listFilesAndDirs(), ponieważ listFiles()nie zwraca pustych folderów.
schnatterer
1
@MikeFHay Patrząc na kod FileUtils, myślę, że tak powinno być FileUtils.listFiles(dir, true, true). użycie FileUtils.listFiles(dir, null, true)spowoduje zgłoszenie wyjątku, a FileUtils.listFiles(dir, true, null)wyświetli listę wszystkich plików bez zaglądania do podkatalogów.
ocramot
Co powiesz na rodzimą bibliotekę JDK? Mogę to łatwo wdrożyć, ale po prostu byłbym C&P z innych miejsc
Christian Bongiorno,
1
Łączę kilka testów, ale jak dotąd wydaje się, że działa 4 razy wolniej niż przy użyciu alternatyw JDK8 lub JDK7. Symlinks również okazują się problematyczne z tym podejściem, szczególnie tam, gdzie prowadzą do katalogów znajdujących się wyżej w drzewie, powoduje to, że metoda nigdy nie powraca, można tego uniknąć poprzez obsługę filtra, ale niestety same dowiązania symboliczne nie są odwiedzane, nawet gdy plik.
Brett Ryan
138

// Gotowy do biegu

import java.io.File;

public class Filewalker {

    public void walk( String path ) {

        File root = new File( path );
        File[] list = root.listFiles();

        if (list == null) return;

        for ( File f : list ) {
            if ( f.isDirectory() ) {
                walk( f.getAbsolutePath() );
                System.out.println( "Dir:" + f.getAbsoluteFile() );
            }
            else {
                System.out.println( "File:" + f.getAbsoluteFile() );
            }
        }
    }

    public static void main(String[] args) {
        Filewalker fw = new Filewalker();
        fw.walk("c:\\" );
    }

}
układacz
źródło
9
Uważaj tylko, że w przypadku dowiązań symbolicznych wskazujących ścieżkę wyżej w hierarchii ścieżek metoda nigdy się nie kończy. Rozważ ścieżkę z dowiązaniem symbolicznym, który wskazuje -> ..
Brett Ryan
2
Jest to po prostu zła implementacja Files.walkFileTree. Polecam, aby ludzie patrzyli na FIles.walkFileTree zamiast próbować samemu go rzucić ... Ma dokładnie taki problem, jak wskazał @BrettRyan.
Tyler Nichols,
Dziękujemy za dołączenie importu java.io.File ;. Tak wiele przykładów zapomina o dołączeniu przestrzeni nazw, a nawet danych typu danych, czyniąc ten przykład punktem wyjścia w podróż odkrywczą. Tutaj ten przykład jest gotowy do uruchomienia. Dzięki.
barrypicker
Ścieżka może się różnić w zależności od tego, gdzie znajduje się plik Filewalker. Stosowanie "/", "./"lub "../"do katalogu, bieżący katalog roboczy i katalogu nadrzędnego, odpowiednio
Moses Kirathe
67

Java 7 będzie miał ma Files.walkFileTree :

Jeśli podasz punkt początkowy i gościa pliku, będzie on wywoływał różne metody na gościu pliku podczas przeglądania pliku w drzewie plików. Oczekujemy, że ludzie będą tego używać, jeśli opracowują kopię rekurencyjną, ruch rekurencyjny, rekursywne usuwanie lub operację rekurencyjną, która ustawia uprawnienia lub wykonuje inną operację na każdym z plików.

Jest teraz cały samouczek Oracle dotyczący tego pytania .

ziewać
źródło
I nigdy nie informuje o zakończeniu marszu.
Farid
25

Nie są potrzebne biblioteki zewnętrzne.
Zwraca kolekcję, dzięki czemu możesz robić, co chcesz z nią po rozmowie.

public static Collection<File> listFileTree(File dir) {
    Set<File> fileTree = new HashSet<File>();
    if(dir==null||dir.listFiles()==null){
        return fileTree;
    }
    for (File entry : dir.listFiles()) {
        if (entry.isFile()) fileTree.add(entry);
        else fileTree.addAll(listFileTree(entry));
    }
    return fileTree;
}
Petrucio
źródło
proste i czyste
Leo
17

Wybrałbym coś takiego:

public void list(File file) {
    System.out.println(file.getName());
    File[] children = file.listFiles();
    for (File child : children) {
        list(child);
    }
}

Plik System.out.println jest po to, aby wskazać, że należy coś zrobić z plikiem. nie ma potrzeby rozróżniania plików i katalogów, ponieważ normalny plik będzie po prostu miał zero elementów potomnych.

Stefan Schmidt
źródło
6
Z dokumentacji listFiles(): „Jeśli ta abstrakcyjna nazwa ścieżki nie oznacza katalogu, wówczas ta metoda zwraca null.”
hfs,
Ulepszony wariant public static Collection <File> listFileTree (File dir) {if (null == dir ||! Dir.isDirectory ()) {return Collections.emptyList (); } final Set <File> fileTree = new HashSet <File> (); for (Wpis pliku: dir.listFiles ()) {if (entry.isFile ()) {fileTree.add (wpis); } else {fileTree.addAll (listFileTree (wpis)); }} return fileTree; }
Ben,
Dla mnie jest to najbardziej zwięzła odpowiedź, która jest rekurencyjna.
WillieT
14

Wolę używać kolejki niż rekurencji do tego rodzaju prostej podróży:

List<File> allFiles = new ArrayList<File>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File("/start/dir/"));
while (!dirs.isEmpty()) {
  for (File f : dirs.poll().listFiles()) {
    if (f.isDirectory()) {
      dirs.add(f);
    } else if (f.isFile()) {
      allFiles.add(f);
    }
  }
}
Benroth
źródło
Ale twój algorytm nie może drukować z wciętymi danymi wyjściowymi. Katalogi i pliki są pomieszane. Jakieś rozwiązanie?
Wei
12

po prostu napisz to sam, używając prostej rekurencji:

public List<File> addFiles(List<File> files, File dir)
{
    if (files == null)
        files = new LinkedList<File>();

    if (!dir.isDirectory())
    {
        files.add(dir);
        return files;
    }

    for (File file : dir.listFiles())
        addFiles(files, file);
    return files;
}
pstanton
źródło
1
Proszę! pozwól programowi wywołującemu zainicjować listę plików, aby nie musiał za każdym razem sprawdzać jego nieważności. Jeśli chcesz utworzyć drugą (publiczną) metodę, która tworzy listę, wywołuje tę metodę wewnętrzną i zwraca pełną listę.
helios
1
cokolwiek. czek zerowy nie jest bardzo drogi, oprócz wygody + osobistych preferencji, myślę, że on zrozumie.
pstanton
Czy możesz wyjaśnić nieco bardziej szczegółowo?
uday
8

Myślę, że to powinno wystarczyć:

File dir = new File(dirname);
String[] files = dir.list();

W ten sposób masz pliki i katalogi. Teraz użyj rekurencji i zrób to samo dla katalogów ( Fileklasa ma isDirectory()metodę).

Michał Niklas
źródło
8

W Javie 7 możesz użyć następującej klasy:

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class MyFileIterator extends SimpleFileVisitor<Path>
{
    public MyFileIterator(String path) throws Exception
    {
        Files.walkFileTree(Paths.get(path), this);
    }

    @Override
    public FileVisitResult visitFile(Path file,
            BasicFileAttributes attributes) throws IOException
    {
        System.out.println("File: " + file);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir,
            BasicFileAttributes attributes) throws IOException
    {
        System.out.println("Dir: " + dir);
        return FileVisitResult.CONTINUE;
    }
}
chao
źródło
7

W Javie 8 możemy teraz użyć narzędzia Pliki do przejścia po drzewie plików. Bardzo prosty.

Files.walk(root.toPath())
      .filter(path -> !Files.isDirectory(path))
      .forEach(path -> System.out.println(path));
Roy Kachouh
źródło
7

Ten kod jest gotowy do uruchomienia

public static void main(String... args) {
    File[] files = new File("D:/").listFiles();
    if (files != null) 
       getFiles(files);
}

public static void getFiles(File[] files) {
    for (File file : files) {
        if (file.isDirectory()) {
            getFiles(file.listFiles());
        } else {
            System.out.println("File: " + file);
        }
    }
}
Ebraheem Alrabee ”
źródło
4

Oprócz przejścia rekurencyjnego można również zastosować podejście oparte na odwiedzających.

Poniższy kod używa podejścia opartego na odwiedzających do przejścia. Oczekuje się, że dane wejściowe do programu to katalog główny do przejścia.

public interface Visitor {
    void visit(DirElement d);
    void visit(FileElement f);
}

public abstract class Element {
    protected File rootPath;
    abstract void accept(Visitor v);

    @Override
    public String toString() {
        return rootPath.getAbsolutePath();
    }
}

public class FileElement extends Element {
    FileElement(final String path) {
        rootPath = new File(path);
    }

    @Override
    void accept(final Visitor v) {
        v.visit(this);
    }
}

public class DirElement extends Element implements Iterable<Element> {
    private final List<Element> elemList;
    DirElement(final String path) {
        elemList = new ArrayList<Element>();
        rootPath = new File(path);
        for (File f : rootPath.listFiles()) {
            if (f.isDirectory()) {
                elemList.add(new DirElement(f.getAbsolutePath()));
            } else if (f.isFile()) {
                elemList.add(new FileElement(f.getAbsolutePath()));
            }
        }
    }

    @Override
    void accept(final Visitor v) {
        v.visit(this);
    }

    public Iterator<Element> iterator() {
        return elemList.iterator();
    }
}

public class ElementWalker {
    private final String rootDir;
    ElementWalker(final String dir) {
        rootDir = dir;
    }

    private void traverse() {
        Element d = new DirElement(rootDir);
        d.accept(new Walker());
    }

    public static void main(final String[] args) {
        ElementWalker t = new ElementWalker("C:\\temp");
        t.traverse();
    }

    private class Walker implements Visitor {
        public void visit(final DirElement d) {
            System.out.println(d);
            for(Element e:d) {
                e.accept(this);
            }
        }

        public void visit(final FileElement f) {
            System.out.println(f);
        }
    }
}
sateesh
źródło
3

Możesz użyć poniższego kodu, aby rekursywnie uzyskać listę plików określonego folderu lub katalogu.

public static void main(String args[]) {

        recusiveList("D:");

    }

    public static void recursiveList(String path) {

        File f = new File(path);
        File[] fl = f.listFiles();
        for (int i = 0; i < fl.length; i++) {
            if (fl[i].isDirectory() && !fl[i].isHidden()) {
                System.out.println(fl[i].getAbsolutePath());
                recusiveList(fl[i].getAbsolutePath());
            } else {
                System.out.println(fl[i].getName());
            }
        }
    }
Rakesh Chaudhari
źródło
2

Odpowiedź akceptowana jest wielka, jednak załamuje, gdy chcesz zrobić IO wewnątrz lambda.

Oto, co możesz zrobić, jeśli Twoje działanie deklaruje wyjątki IO.

Możesz traktować filtrowany strumień jako Iterable, a następnie wykonywać swoje czynności w regularnej pętli dla każdego. W ten sposób nie musisz radzić sobie z wyjątkami wewnątrz lambda.

try (Stream<Path> pathStream = Files.walk(Paths.get(path))
        .filter(Files::isRegularFile)) {

    for (Path file : (Iterable<Path>) pathStream::iterator) {
        // something that throws IOException
        Files.copy(file, System.out);
    }
}

Znalazłem tę sztuczkę tutaj: https://stackoverflow.com/a/32668807/1207791

cfstras
źródło
1

Nierekurencyjny BFS z pojedynczą listą (szczególnym przykładem jest wyszukiwanie plików * .eml):

    final FileFilter filter = new FileFilter() {
        @Override
        public boolean accept(File file) {
            return file.isDirectory() || file.getName().endsWith(".eml");
        }
    };

    // BFS recursive search
    List<File> queue = new LinkedList<File>();
    queue.addAll(Arrays.asList(dir.listFiles(filter)));

    for (ListIterator<File> itr = queue.listIterator(); itr.hasNext();) {
        File file = itr.next();
        if (file.isDirectory()) {
            itr.remove();
            for (File f: file.listFiles(filter)) itr.add(f);
        }
    }
Bobah
źródło
1

Moja wersja (oczywiście mogłem skorzystać z wbudowanego przejścia w Javie 8 ;-)):

public static List<File> findFilesIn(File rootDir, Predicate<File> predicate) {
        ArrayList<File> collected = new ArrayList<>();
        walk(rootDir, predicate, collected);
        return collected;
    }

    private static void walk(File dir, Predicate<File> filterFunction, List<File> collected) {
        Stream.of(listOnlyWhenDirectory(dir))
                .forEach(file -> walk(file, filterFunction, addAndReturn(collected, file, filterFunction)));
    }

    private static File[] listOnlyWhenDirectory(File dir) {
        return dir.isDirectory() ? dir.listFiles() : new File[]{};
    }

    private static List<File> addAndReturn(List<File> files, File toAdd, Predicate<File> filterFunction) {
        if (filterFunction.test(toAdd)) {
            files.add(toAdd);
        }
        return files;
    }
użytkownik1189332
źródło
1

Oto proste, ale doskonale działające rozwiązanie wykorzystujące recursion:

public static List<Path> listFiles(String rootDirectory)
{
    List<Path> files = new ArrayList<>();
    listFiles(rootDirectory, files);

    return files;
}

private static void listFiles(String path, List<Path> collectedFiles)
{
    File root = new File(path);
    File[] files = root.listFiles();

    if (files == null)
    {
        return;
    }

    for (File file : files)
    {
        if (file.isDirectory())
        {
            listFiles(file.getAbsolutePath(), collectedFiles);
        } else
        {
            collectedFiles.add(file.toPath());
        }
    }
}
BullyWiiPlaza
źródło
1
    private void fillFilesRecursively(File file, List<File> resultFiles) {
        if (file.isFile()) {
            resultFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                fillFilesRecursively(child, resultFiles);
            }
        }
    }
legendmohe
źródło
1

Wymyśliłem to, aby rekursywnie drukować wszystkie pliki / nazwy plików.

private static void printAllFiles(String filePath,File folder) {
    if(filePath==null) {
        return;
    }
    File[] files = folder.listFiles();
    for(File element : files) {
        if(element.isDirectory()) {
            printAllFiles(filePath,element);
        } else {
            System.out.println(" FileName "+ element.getName());
        }
    }
}
kanaparthikiran
źródło
0

Przykładowe dane wyjściowe * .csv w katalogu przeszukiwanie cykliczne podkatalogów za pomocą Files.find () z java.nio:

String path = "C:/Daten/ibiss/ferret/";
    logger.debug("Path:" + path);
    try (Stream<Path> fileList = Files.find(Paths.get(path), Integer.MAX_VALUE,
            (filePath, fileAttr) -> fileAttr.isRegularFile() && filePath.toString().endsWith("csv"))) {
        List<String> someThingNew = fileList.sorted().map(String::valueOf).collect(Collectors.toList());
        for (String t : someThingNew) {
            t.toString();
            logger.debug("Filename:" + t);
        }

    }

Publikując ten przykład, ponieważ miałem problem ze zrozumieniem, jak przekazać parametr filename w przykładzie nr 1 podanym przez Bryana, używając foreach na Stream-result -

Mam nadzieję że to pomoże.

Ralf R.
źródło
0

Kotlin ma FileTreeWalkdo tego celu. Na przykład:

dataDir.walkTopDown().filter { !it.isDirectory }.joinToString("\n") {
   "${it.toRelativeString(dataDir)}: ${it.length()}"
}

Utworzy listę tekstową wszystkich plików innych niż katalogi w danym katalogu głównym, po jednym pliku w wierszu ze ścieżką względem katalogu głównego i długością.

Clyde
źródło
0

Innym sposobem, który możesz zrobić, nawet jeśli ktoś już udostępnia spacer Java 8.

Ten zapewni Ci rekursywnie wszystkie pliki

  private Stream<File> files(File file) {
    return file.isDirectory()
            ? Arrays.stream(file.listFiles()).flatMap(this::files)
            : Stream.of(file);
}
Michael
źródło
-1

Na podstawie odpowiedzi układacza. Oto rozwiązanie działające w JSP bez żadnych zewnętrznych bibliotek, dzięki czemu możesz umieścić je prawie w dowolnym miejscu na serwerze:

<!DOCTYPE html>
<%@ page session="false" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page contentType="text/html; charset=UTF-8" %>

<%!
    public List<String> files = new ArrayList<String>();
    /**
        Fills files array with all sub-files.
    */
    public void walk( File root ) {
        File[] list = root.listFiles();

        if (list == null) return;

        for ( File f : list ) {
            if ( f.isDirectory() ) {
                walk( f );
            }
            else {
                files.add(f.getAbsolutePath());
            }
        }
    }
%>
<%
    files.clear();
    File jsp = new File(request.getRealPath(request.getServletPath()));
    File dir = jsp.getParentFile();
    walk(dir);
    String prefixPath = dir.getAbsolutePath() + "/";
%>

Następnie po prostu robisz coś takiego:

    <ul>
        <% for (String file : files) { %>
            <% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
                <li><%=file.replace(prefixPath, "")%></li>
            <% } %>
        <% } %>
    </ul>
Nux
źródło
1
Chociaż prawdopodobnie działa, pytanie dotyczy przeglądania plików, a nie renderowania przeglądanych plików. Lepiej ujawnij swój algorytm jako taki, nie jest zalecaną praktyką osadzanie logiki biznesowej w JSP.
Samuel Kerrien,
To zależy od tego, co robisz. W aplikacji dla przedsiębiorstw masz absolutną rację. Jeśli potrzebujesz tego tylko jako wpisu do prostej, samodzielnej listy, to jest w porządku.
Nux