Daftar file secara rekursif di Jawa

258

Bagaimana cara mendaftarkan semua file secara rekursif di bawah direktori di Java? Apakah kerangka kerja menyediakan utilitas?

Saya melihat banyak implementasi hacky. Tapi tidak ada dari framework atau nio

Quintin Par
sumber
2
Saya baru saja menyelesaikan Hasil Tes yang menyediakan tes kinerja untuk banyak jawaban. Tidak mengherankan semua jawaban berbasis NIO berkinerja terbaik. Jawaban umum jelas berkinerja terburuk dengan lebih dari dua kali panjang lari.
Brett Ryan
2
Java8: Files.walk?
Benj

Jawaban:

327

Java 8 menyediakan aliran yang bagus untuk memproses semua file dalam pohon.

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

Ini memberikan cara alami untuk melintasi file. Karena ini adalah streaming, Anda dapat melakukan semua operasi streaming yang bagus pada hasil seperti batas, pengelompokan, pemetaan, keluar lebih awal, dll.

UPDATE : Saya mungkin menunjukkan ada juga Files.find yang mengambil BiPredicate yang bisa lebih efisien jika Anda perlu memeriksa atribut file.

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

Perhatikan bahwa sementara JavaDoc menghindari bahwa metode ini bisa lebih efisien daripada Files.walk secara efektif identik, perbedaan dalam kinerja dapat diamati jika Anda juga mengambil atribut file dalam filter Anda. Pada akhirnya, jika Anda perlu memfilter pada atribut menggunakan Files.find , sebaliknya gunakan Files.walk , sebagian besar karena ada kelebihan dan lebih nyaman.

UJI : Seperti yang diminta, saya telah memberikan perbandingan kinerja dari banyak jawaban. Lihat proyek Github yang berisi hasil dan uji kasus .

Brett Ryan
sumber
6
Salah satu contoh yang dapat menunjukkan keajaiban pemrograman fungsional bahkan untuk pemula.
Johnny
2
Bagaimana kinerja ini dibandingkan dengan metode pra-java 8? Traversal direktori saya saat ini terlalu lambat dan saya sedang mencari sesuatu yang akan mempercepatnya.
Sridhar Sarnobat
1
Saya menulis beberapa tes yang mengandung sebagian besar varian dalam jawaban yang diberikan. Sejauh ini, tampaknya menggunakan Files.walkdengan aliran paralel adalah yang terbaik, diikuti oleh Files.walkFileTreeyang hanya sedikit lebih lambat. Jawaban yang diterima menggunakan commons-io sejauh ini paling lambat oleh tes saya menjadi 4 kali lebih lambat.
Brett Ryan
1
@ BrettRyan, saya mencoba solusi Anda, tetapi saya mendapatkan pengecualian Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException. Bagaimana saya bisa memperbaikinya
Kachna
5
Bagaimana cara saya mendapatkan daftar file yang sebenarnya dari ini?
thouliha
159

FileUtils punya iterateFilesdan listFilesmetode. Cobalah mereka. (dari commons-io )

Sunting: Anda dapat memeriksa di sini untuk benchmark dari berbagai pendekatan. Tampaknya pendekatan commons-io lambat, jadi pilih yang lebih cepat dari sini (jika itu penting)

Bozho
sumber
40
FYI / TLDR: jika Anda hanya ingin mendaftar semua file secara rekursif tanpa penyaringan, lakukan FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE), di mana dirobjek File yang menunjuk ke direktori dasar.
andronikus
2
Anda mungkin ingin mempertimbangkan untuk menggunakan listFilesAndDirs(), karena listFiles()tidak mengembalikan folder kosong.
schnatterer
1
@ MikeFHay Melihat kode FileUtils, saya pikir itu akan menjadi FileUtils.listFiles(dir, true, true). menggunakan FileUtils.listFiles(dir, null, true)akan membuang Pengecualian, sementara FileUtils.listFiles(dir, true, null)akan daftar semua file tanpa melihat ke subdirektori.
ocramot
Bagaimana dengan perpustakaan asli JDK? Saya bisa menerapkan ini dengan mudah tetapi saya hanya akan menjadi C&P dari tempat lain
Christian Bongiorno
1
Saya sedang melakukan beberapa tes bersama, tetapi sejauh ini ini tampaknya melakukan 4 kali lebih lambat daripada menggunakan alternatif JDK8 atau JDK7. Symlinks juga terbukti bermasalah dengan pendekatan ini, terutama di mana mereka menautkan ke direktori yang lebih tinggi di pohon, ini menyebabkan metode tidak pernah kembali, ini dapat dihindari dengan menangani filter, tetapi sayangnya symlinks sendiri tidak dikunjungi walaupun file.
Brett Ryan
138

// Siap untuk berlari

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:\\" );
    }

}
stacker
sumber
9
Berhati-hatilah bahwa untuk tautan simbolik yang mengarah ke jalur yang lebih tinggi dalam hierarki jalur akan menyebabkan metode ini tidak pernah berakhir. Pertimbangkan jalur dengan symlink yang menunjuk -> ..
Brett Ryan
2
Ini pada dasarnya hanyalah implementasi Files.walkFileTree yang buruk. Saya akan merekomendasikan bahwa orang melihat FIles.walkFileTree daripada mencoba untuk menggulung sendiri ... Ini telah menangani masalah yang tepat @BrettRyan mengarahkannya.
Tyler Nichols
Terima kasih telah memasukkan impor java.io.File ;. Begitu banyak contoh lupa untuk memasukkan hal-hal namespace atau bahkan hal-hal tipe data membuat contoh titik awal pada perjalanan penemuan. Di sini contoh ini siap dijalankan. Terima kasih.
barrypicker
Path dapat bervariasi tergantung di mana file Filewalker berada. Gunakan "/", "./"atau "../"untuk direktori root, direktori kerja saat ini, dan direktori induk, masing
Moses Kirathe
67

Java 7 akan memiliki Files.walkFileTree :

Jika Anda memberikan titik awal dan pengunjung file, itu akan memanggil berbagai metode pada pengunjung file saat berjalan melalui file di pohon file. Kami berharap orang untuk menggunakan ini jika mereka mengembangkan salinan rekursif, gerakan rekursif, penghapusan rekursif, atau operasi rekursif yang menetapkan izin atau melakukan operasi lain pada masing-masing file.

Sekarang ada seluruh tutorial Oracle untuk pertanyaan ini .

menguap
sumber
Dan itu tidak pernah memberi tahu ujung jalan.
Farid
25

Tidak diperlukan perpustakaan eksternal.
Mengembalikan Koleksi sehingga Anda dapat melakukan apa pun yang Anda inginkan dengannya setelah panggilan.

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
sumber
sederhana dan bersih
Leo
17

Saya akan pergi dengan sesuatu seperti:

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

System.out.println ada di sana hanya untuk menunjukkan untuk melakukan sesuatu dengan file tersebut. tidak perlu membedakan antara file dan direktori, karena file normal hanya akan memiliki nol anak.

Stefan Schmidt
sumber
6
Dari dokumentasi listFiles(): "Jika pathname abstrak ini tidak menunjukkan direktori, maka metode ini kembali null."
hfs
Varian Koleksi statis publik yang ditingkatkan <File> listFileTree (File dir) {if (null == dir ||! Dir.isDirectory ()) {return Collections.emptyList (); } set terakhir <File> fileTree = HashSet baru <File> (); untuk (Entri file: dir.listFiles ()) {if (entry.isFile ()) {fileTree.add (entri); } else {fileTree.addAll (listFileTree (entri)); }} kembalikan fileTree; }
Ben
Bagi saya ini adalah jawaban paling ringkas yang bersifat rekursif.
WillieT
14

Saya lebih suka menggunakan antrian atas rekursi untuk jenis traversi sederhana ini:

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
sumber
Tetapi algoritme Anda tidak dapat mencetak dengan output yang berlekuk. Dir dan file berantakan. Ada solusi?
Wei
12

cukup tulis sendiri menggunakan rekursi sederhana:

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
sumber
1
Silahkan! biarkan penelepon menginisialisasi daftar file sehingga tidak harus memeriksa nolnya setiap kali. Jika Anda ingin membuat metode kedua (publik) yang membuat daftar, panggil metode internal ini dan kembalikan daftar lengkap.
helios
1
Masa bodo. cek nol tidak terlalu mahal, kenyamanan + selain preferensi pribadi saya pikir dia akan mengerti intinya.
pstanton
Bisakah Anda menjelaskan sedikit lebih banyak?
uday
8

Saya pikir ini harus dilakukan:

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

Dengan cara ini Anda memiliki file dan direktori. Sekarang gunakan rekursi dan lakukan hal yang sama untuk dirs ( Fileclass has isDirectory()method).

Michał Niklas
sumber
8

Dengan Java 7 Anda dapat menggunakan kelas berikut:

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
sumber
7

Di Java 8, sekarang kita dapat menggunakan utilitas File untuk menjalankan hierarki file. Sangat sederhana.

Files.walk(root.toPath())
      .filter(path -> !Files.isDirectory(path))
      .forEach(path -> System.out.println(path));
Roy Kachouh
sumber
7

Kode ini siap dijalankan

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 '
sumber
4

Terlepas dari traversal rekursif seseorang dapat menggunakan pendekatan berbasis Pengunjung juga.

Kode di bawah ini menggunakan pendekatan berbasis Pengunjung untuk traversal. Diharapkan input ke program adalah direktori root untuk dilalui.

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
sumber
3

Anda dapat menggunakan kode di bawah ini untuk mendapatkan daftar file folder atau direktori tertentu secara rekursif.

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
sumber
2

The jawaban yang diterima besar, namun itu rusak ketika Anda ingin melakukan IO dalam lambda.

Inilah yang dapat Anda lakukan jika tindakan Anda menyatakan IOExceptions.

Anda dapat memperlakukan aliran yang difilter sebagai Iterable, dan kemudian melakukan tindakan Anda secara teratur untuk setiap loop. Dengan cara ini, Anda tidak perlu menangani pengecualian di dalam 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);
    }
}

Menemukan trik itu di sini: https://stackoverflow.com/a/32668807/1207791

cfstras
sumber
1

BFS non-rekursif dengan satu daftar (contoh khusus mencari file * .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
sumber
1

Versi saya (tentu saja saya bisa menggunakan built in walk di Java 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;
    }
pengguna1189332
sumber
1

Di sini solusi sederhana namun berfungsi sempurna menggunakan 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
sumber
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
sumber
1

Saya datang dengan ini untuk mencetak semua file / nama file secara rekursif.

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
sumber
0

Contoh menampilkan * .csv file dalam direktori Subdirectories pencarian rekursif menggunakan Files.find () dari 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);
        }

    }

Memposting contoh ini, karena saya mengalami kesulitan memahami cara melewati parameter nama file dalam contoh # 1 yang diberikan oleh Bryan, menggunakan foreach pada hasil-Stream -

Semoga ini membantu.

Ralf R.
sumber
0

Kotlin memiliki FileTreeWalktujuan ini. Sebagai contoh:

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

Akan menghasilkan daftar teks semua file non-direktori di bawah root yang diberikan, satu file per baris dengan path relatif ke root dan panjangnya.

Clyde
sumber
0

Cara lain yang dapat Anda lakukan bahkan jika seseorang sudah menyediakan Java 8 walk.

Yang ini akan memberi Anda semua file secara rekursif

  private Stream<File> files(File file) {
    return file.isDirectory()
            ? Arrays.stream(file.listFiles()).flatMap(this::files)
            : Stream.of(file);
}
Michael
sumber
-1

Berdasarkan jawaban stacker. Berikut ini adalah solusi yang bekerja di JSP tanpa pustaka eksternal sehingga Anda dapat meletakkannya hampir di mana saja di server Anda:

<!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() + "/";
%>

Maka Anda hanya melakukan sesuatu seperti:

    <ul>
        <% for (String file : files) { %>
            <% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
                <li><%=file.replace(prefixPath, "")%></li>
            <% } %>
        <% } %>
    </ul>
Nux
sumber
1
Meskipun mungkin berhasil, pertanyaannya adalah tentang penelusuran file, bukan rendering file yang diramban. Lebih baik paparkan algoritme Anda dengan demikian, bukan praktik yang disarankan untuk menyematkan logika bisnis di dalam JSP.
Samuel Kerrien
Itu tergantung apa yang Anda lakukan. Dalam aplikasi ukuran perusahaan Anda benar sekali. Jika Anda hanya memerlukan ini sebagai drop-in ke daftar sederhana dan mandiri, maka ini tidak masalah.
Nux