Liệt kê đệ quy các tệp trong Java


258

Làm cách nào để liệt kê đệ quy tất cả các tệp trong một thư mục trong Java? Liệu khung cung cấp bất kỳ tiện ích?

Tôi đã thấy rất nhiều triển khai hacky. Nhưng không ai trong khuôn khổ hoặc nio


2
Tôi vừa hoàn thành Kết quả kiểm tra cung cấp các bài kiểm tra hiệu suất cho nhiều câu trả lời. Không có gì ngạc nhiên khi tất cả các câu trả lời dựa trên NIO hoạt động tốt nhất. Câu trả lời commons-io rõ ràng là trình diễn tệ nhất với hơn hai lần thời lượng chạy.
Brett Ryan

2
Java8: Files.walk?
Đến

Câu trả lời:


327

Java 8 cung cấp một luồng tốt để xử lý tất cả các tệp trong cây.

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

Điều này cung cấp một cách tự nhiên để duyệt qua các tập tin. Vì đó là luồng nên bạn có thể thực hiện tất cả các thao tác luồng tốt trên kết quả như giới hạn, nhóm, ánh xạ, thoát sớm, v.v.

CẬP NHẬT : Tôi có thể chỉ ra rằng đó cũng là Files.findBiPredicate có thể hiệu quả hơn nếu bạn cần kiểm tra các thuộc tính tệp.

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

Lưu ý rằng mặc dù JavaDoc cho rằng phương thức này có thể hiệu quả hơn Files.walk giống hệt nhau, nhưng sự khác biệt về hiệu suất có thể được quan sát nếu bạn cũng đang truy xuất các thuộc tính tệp trong bộ lọc của mình. Cuối cùng, nếu bạn cần lọc các thuộc tính, hãy sử dụng Files.find , nếu không thì sử dụng Files.walk , chủ yếu là vì có quá tải và thuận tiện hơn.

KIỂM TRA : Theo yêu cầu, tôi đã cung cấp một so sánh hiệu suất của nhiều câu trả lời. Kiểm tra dự án Github có chứa kết quả và trường hợp thử nghiệm .


6
Một trong những ví dụ có thể cho thấy sự kỳ diệu của lập trình chức năng ngay cả đối với người mới bắt đầu.
Johnny

2
Làm thế nào để hiệu suất của điều này so sánh với các phương pháp tiền java 8? Chuyển đổi thư mục hiện tại của tôi quá chậm và tôi đang tìm kiếm thứ gì đó sẽ tăng tốc nó.
Sridhar Sarnobat

1
Tôi đang viết một số bài kiểm tra có chứa hầu hết các biến thể trong các câu trả lời được cung cấp. Cho đến nay, có vẻ như sử dụng Files.walkvới một luồng song song là tốt nhất, theo sát với Files.walkFileTreenó chỉ chậm hơn một chút. Câu trả lời được chấp nhận bằng commons-io là chậm nhất bởi các bài kiểm tra của tôi chậm hơn 4 lần.
Brett Ryan

1
@BrettRyan, tôi đã thử giải pháp của bạn nhưng tôi nhận được một ngoại lệ Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException. Làm thế nào tôi có thể sửa nó
Kachna

5
Làm thế nào để tôi có được một danh sách thực tế của các tập tin từ đây?
thouliha

159

FileUtilsiterateFileslistFilesphương thức. Hãy cho họ một thử. (từ commons-io )

Chỉnh sửa: Bạn có thể kiểm tra ở đây để biết điểm chuẩn của các phương pháp khác nhau. Có vẻ như cách tiếp cận commons-io rất chậm, vì vậy hãy chọn một số cách nhanh hơn từ đây (nếu có vấn đề)


40
FYI / TLDR: nếu bạn chỉ muốn liệt kê tất cả các tệp theo cách đệ quy mà không lọc, hãy làm FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE), đâu dirlà một đối tượng Tệp trỏ đến thư mục cơ sở.
andronikus

2
Bạn có thể muốn xem xét sử dụng listFilesAndDirs(), vì listFiles()không trả lại các thư mục trống.
schnatterer

1
@MikeFHay Nhìn vào mã FileUtils, tôi nghĩ rằng nó nên FileUtils.listFiles(dir, true, true). bằng cách sử dụng FileUtils.listFiles(dir, null, true)sẽ ném Ngoại lệ, trong khi FileUtils.listFiles(dir, true, null)sẽ liệt kê tất cả các tệp mà không cần xem xét các thư mục con.
ocramot

Làm thế nào về một thư viện bản địa JDK? Tôi có thể thực hiện điều này dễ dàng nhưng tôi chỉ đơn giản là C & P từ những nơi khác
Christian Bongiorno

1
Tôi đang thực hiện một số thử nghiệm cùng nhau, nhưng cho đến nay điều này dường như đang thực hiện chậm hơn 4 lần so với việc sử dụng các lựa chọn thay thế JDK8 hoặc JDK7. Symlinks cũng tỏ ra có vấn đề với cách tiếp cận này, đặc biệt là khi chúng liên kết đến các thư mục cao hơn trên cây, điều này khiến phương thức không bao giờ quay trở lại, điều này có thể tránh được bằng cách xử lý bộ lọc, nhưng tiếc là chính các liên kết tượng trưng không được truy cập ngay cả khi một tập tin.
Brett Ryan

138

// Sẵn sàng để chạy

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

}

9
Chỉ cần lưu ý rằng các liên kết tượng trưng chỉ đến một đường dẫn cao hơn trong hệ thống phân cấp đường dẫn sẽ khiến phương thức không bao giờ kết thúc. Xem xét một đường dẫn với một liên kết tượng trưng đến -> ..
Brett Ryan

2
Đây thực chất chỉ là một triển khai tồi của Files.walkFileTree. Tôi sẽ đề nghị mọi người nhìn vào FIles.walkFileTree thay vì cố gắng tự cuộn nó ... Nó đã xử lý vấn đề chính xác @BrettRyan đã chỉ ra nó.
Tyler Nichols

Cảm ơn bạn đã bao gồm nhập java.io.File;. Vì vậy, nhiều ví dụ quên bao gồm các công cụ không gian tên hoặc thậm chí các công cụ kiểu dữ liệu làm cho ví dụ trở thành điểm khởi đầu trong hành trình khám phá. Ở đây ví dụ này đã sẵn sàng để chạy. Cảm ơn.
barrypicker

Đường dẫn có thể thay đổi tùy thuộc vào vị trí tệp Filewalker. Sử dụng "/", "./"hoặc "../"cho thư mục gốc, thư mục làm việc hiện tại và thư mục mẹ, tương ứng
Moses Kiredit

67

Java 7 sẽ cóFiles.walkFileTree :

Nếu bạn cung cấp một điểm bắt đầu và một khách truy cập tệp, nó sẽ gọi các phương thức khác nhau trên khách truy cập tệp khi nó đi qua tệp trong cây tệp. Chúng tôi hy vọng mọi người sẽ sử dụng điều này nếu họ đang phát triển một bản sao đệ quy, di chuyển đệ quy, xóa đệ quy hoặc thao tác đệ quy đặt quyền hoặc thực hiện một thao tác khác trên mỗi tệp.

Hiện tại có toàn bộ hướng dẫn của Oracle về câu hỏi này .


Và nó không bao giờ thông báo kết thúc đi bộ.
Farid

25

Không cần thư viện bên ngoài.
Trả về Bộ sưu tập để bạn có thể làm bất cứ điều gì bạn muốn với nó sau cuộc gọi.

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;
}

đơn giản và sạch sẽ
Leo

17

Tôi sẽ đi với một cái gì đó như:

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

System.out.println chỉ ở đó để chỉ ra để làm một cái gì đó với tệp. không cần phân biệt giữa các tệp và thư mục, vì một tệp bình thường sẽ không có con.


6
Từ tài liệu của listFiles(): Viking Nếu tên đường dẫn trừu tượng này không biểu thị một thư mục, thì phương thức này trả về null.
hfs

Bộ sưu tập tĩnh công khai biến thể được cải tiến <File> listFileTree (File dir) {if (null == dir ||! Dir.isDirectory ()) {return Collections.emptyList (); } bộ cuối cùng <File> fileTree = new Hashset <File> (); for (Mục nhập tệp: dir.listFiles ()) {if (entry.isFile ()) {fileTree.add (entry); } other {fileTree.add ALL (listFileTree (entry)); }} trả lại fileTree; }
Ben

Đối với tôi đây là câu trả lời ngắn gọn nhất được đệ quy.
WillieT

14

Tôi thích sử dụng một hàng đợi trên đệ quy cho loại chuyển đổi đơn giản nà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);
    }
  }
}

Nhưng thuật toán của bạn không thể in với đầu ra thụt lề. Dirs và tập tin bị rối. Bất kì giải pháp nào?
Ngụy

12

chỉ cần viết nó bằng cách sử dụng đệ quy đơn giản:

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;
}

1
Xin vui lòng! hãy để người gọi khởi tạo danh sách tập tin để nó không phải kiểm tra tính vô hiệu của nó mỗi lần. Nếu bạn muốn tạo phương thức thứ hai (công khai) để tạo danh sách, hãy gọi phương thức nội bộ này và trả về danh sách đầy đủ.
helios

1
bất cứ điều gì. một kiểm tra null không quá đắt, sự tiện lợi + sở thích cá nhân sang một bên tôi nghĩ rằng anh ta sẽ nhận được điểm.
pstanton

Bạn có thể giải thích một chút bằng lời nói?
13 lúc 22 giờ 34

8

Tôi nghĩ rằng điều này nên làm công việc:

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

Bằng cách này bạn có tập tin và thư mục. Bây giờ sử dụng đệ quy và làm tương tự cho các thư mục ( Filelớp có isDirectory()phương thức).


8

Với Java 7, bạn có thể sử dụng lớp sau:

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;
    }
}

7

Trong Java 8, bây giờ chúng ta có thể sử dụng tiện ích Tệp để đi bộ cây tệp. Rất đơn giản.

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

7

Mã này đã sẵn sàng để chạy

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);
        }
    }
}

4

Ngoài phương thức đệ quy, người ta cũng có thể sử dụng cách tiếp cận dựa trên Khách truy cập.

Dưới đây mã được sử dụng phương pháp tiếp cận dựa trên khách truy cập cho traversal. Dự kiến ​​rằng đầu vào của chương trình là thư mục gốc để duyệt qua.

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);
        }
    }
}

3

Bạn có thể sử dụng mã dưới đây để nhận danh sách các tệp của thư mục hoặc thư mục cụ thể theo cách đệ quy.

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());
            }
        }
    }

2

Các câu trả lời được chấp nhận là rất tốt, tuy nhiên nó bị phá vỡ khi bạn muốn làm IO bên trong lambda.

Dưới đây là những gì bạn có thể làm nếu hành động của bạn tuyên bố IOExceptions.

Bạn có thể coi luồng được lọc là một Iterable, và sau đó thực hiện hành động của mình trong một vòng lặp cho mỗi vòng lặp thông thường. Bằng cách này, bạn không phải xử lý ngoại lệ trong 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);
    }
}

Tìm thấy mánh khóe đó tại đây: https://stackoverflow.com/a/32668807/1207791


1

BFS không đệ quy với một danh sách (ví dụ cụ thể là tìm kiếm tệp * .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);
        }
    }

1

Phiên bản của tôi (tất nhiên tôi có thể đã sử dụng bản dựng sẵn trong 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;
    }

1

Đây là một giải pháp đơn giản nhưng hoàn hảo làm việc bằng cách sử dụng 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());
        }
    }
}

1
    private void fillFilesRecursively(File file, List<File> resultFiles) {
        if (file.isFile()) {
            resultFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                fillFilesRecursively(child, resultFiles);
            }
        }
    }

1

Tôi đã đưa ra điều này để in tất cả các tệp / tên tệp đệ quy.

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());
        }
    }
}

0

Ví dụ đầu ra các tệp * .csv trong thư mục tìm kiếm đệ quy thư mục con bằng Files.find () từ 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);
        }

    }

Đăng ví dụ này, vì tôi gặp khó khăn trong việc hiểu cách vượt qua tham số tên tệp trong ví dụ số 1 do Bryan đưa ra, sử dụng foreach trên Stream-result -

Hi vọng điêu nay co ich.


0

Kotlin có FileTreeWalkmục đích này. Ví dụ:

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

Sẽ tạo ra một danh sách văn bản của tất cả các tệp không phải thư mục dưới một gốc nhất định, một tệp trên mỗi dòng có đường dẫn liên quan đến gốc và độ dài.


0

Một cách khác bạn có thể làm ngay cả khi ai đó đã cung cấp Java 8 walk.

Điều này sẽ cung cấp cho bạn tất cả các tệp đệ quy

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

-1

Dựa trên câu trả lời stacker. Đây là một giải pháp hoạt động trong JSP mà không cần bất kỳ thư viện bên ngoài nào để bạn có thể đặt nó ở hầu hết mọi nơi trên máy chủ của mình:

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

Sau đó, bạn chỉ cần làm một cái gì đó như:

    <ul>
        <% for (String file : files) { %>
            <% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
                <li><%=file.replace(prefixPath, "")%></li>
            <% } %>
        <% } %>
    </ul>

1
Trong khi nó có thể hoạt động, câu hỏi là về duyệt tệp, không hiển thị các tệp được duyệt. Tốt hơn là phơi bày thuật toán của bạn như vậy, nó không phải là một cách thực hành được khuyến nghị để nhúng logic kinh doanh vào bên trong một tệp JSP.
Samuel Kerrien

Điều đó phụ thuộc vào những gì bạn đang làm. Trong một ứng dụng quy mô doanh nghiệp, bạn hoàn toàn đúng. Nếu bạn chỉ cần điều này như một danh sách thả vào một danh sách độc lập, đơn giản, thì điều này là hoàn toàn tốt.
Nux
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.