Liệt kê tất cả các tệp từ một thư mục một cách đệ quy với Java


85

Tôi có chức năng này in tên của tất cả các tệp trong một thư mục một cách đệ quy. Vấn đề là mã của tôi rất chậm vì nó phải truy cập thiết bị mạng từ xa với mỗi lần lặp lại.

Kế hoạch của tôi trước tiên là tải tất cả các tệp từ thư mục một cách đệ quy và sau đó đi qua tất cả các tệp bằng regex để lọc ra tất cả các tệp tôi không muốn. Có ai có một đề nghị tốt hơn?

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

Đây chỉ là một bài kiểm tra sau này, tôi sẽ không sử dụng mã như thế này, thay vào đó tôi sẽ thêm đường dẫn và ngày sửa đổi của mọi tệp khớp với regex nâng cao vào một mảng.


1
... câu hỏi là gì? Bạn chỉ đang tìm kiếm xác nhận rằng mã này sẽ hoạt động?
Richard JP Le Guen

Không, tôi biết mã này hoạt động nhưng nó rất chậm và có cảm giác như thật ngu ngốc khi truy cập vào hệ thống tệp và lấy nội dung cho mọi thư mục con thay vì nhận mọi thứ cùng một lúc.
Hultner

Câu trả lời:


134

Giả sử đây là mã sản xuất thực tế mà bạn sẽ viết, thì tôi khuyên bạn nên sử dụng giải pháp cho loại thứ này đã được giải quyết - cụ thể là Apache Commons IOFileUtils.listFiles() . Nó xử lý các thư mục, bộ lọc lồng nhau (dựa trên tên, thời gian sửa đổi, v.v.).

Ví dụ, đối với regex của bạn:

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

Thao tác này sẽ tìm kiếm đệ quy các tệp phù hợp với ^(.*?)regex, trả về kết quả dưới dạng một tập hợp.

Cần lưu ý rằng điều này sẽ không nhanh hơn so với việc cuộn mã của riêng bạn, nó cũng hoạt động tương tự - truy xuất hệ thống tệp trong Java chỉ chậm. Sự khác biệt là, phiên bản Apache Commons sẽ không có lỗi trong đó.


Tôi đã xem ở đó và từ đó tôi sẽ sử dụng commons.apache.org/io/api-release/index.html?org/apache/commons/… để lấy tất cả tệp từ thư mục và thư mục con, sau đó tìm kiếm trong các tệp để chúng phù hợp với regex của tôi. Hoặc là tôi sai?
Hultner

Vâng, vấn đề là phải mất hơn một giờ để quét thư mục và làm điều đó mỗi khi tôi khởi động chương trình để kiểm tra các bản cập nhật là cực kỳ khó chịu. Sẽ nhanh hơn nếu tôi viết phần này của chương trình bằng C và phần còn lại bằng Java và nếu vậy thì có khác biệt đáng kể nào không? Hiện tại, tôi đã thay đổi mã trên dòng if isdir và thêm vào để thư mục cũng phải khớp với regex để được đưa vào tìm kiếm. Tôi thấy rằng trong ví dụ của bạn có ghi DirectoryFileFilter.DIRECTORY, tôi nghĩ rằng tôi có thể có một bộ lọc regex ở đó.
Hultner

1
viết nó bằng cách sử dụng các cuộc gọi riêng sẽ hoàn toàn làm cho nó nhanh hơn - FindFirstFile / FineNextFile cho phép bạn truy vấn các thuộc tính tệp mà không cần phải thực hiện một lệnh gọi riêng - điều này có thể có ý nghĩa lớn đối với các mạng có độ trễ cao hơn. Cách tiếp cận của Java đối với điều này là không hiệu quả kinh khủng.
Ngày của Kevin,

5
@ hanzallah-afgan: Cả câu hỏi và câu trả lời đều trên 5 tuổi. Đã có hai bản phát hành Java lớn trong thời gian đã qua nên bạn có thể không tìm hiểu các tính năng mới hơn như Java 7 NIO.
Hultner

4
Chỉ sử dụng FileUtils nếu bạn biết và chấp nhận lần truy cập hiệu suất: github.com/brettryan/io-recurse-tests . Lựa chọn thay thế java8 gốc cho phép cho một ngắn gọn và hiệu quả hơn ký hiệu ví dụ như:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza

64

Trong Java 8, đó là một 1-liner qua Files.find()với độ sâu tùy tiện lớn (ví dụ 999) và BasicFileAttributescủaisRegularFile()

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

Để thêm tính năng lọc, hãy nâng cao lambda, ví dụ: tất cả các tệp jpg được sửa đổi trong 24 giờ qua:

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

3
Tôi đề nghị luôn luôn sử dụng những phương pháp tập tin mà trả về suối trong khối try-với-nguồn: nếu không bạn sẽ giữ cho nguồn mở
riccardo.tasso

Các hoạt động đầu cuối không tự gọi đóng trên luồng?
Dragas

@Dragas vâng. Người tiêu dùng của tôi chỉ là một ví dụ đơn giản; trong cuộc sống thực, bạn sẽ làm điều gì đó hữu ích hơn.
Bohemian

27

Đây là một phương pháp đệ quy rất đơn giản để lấy tất cả các tệp từ một gốc nhất định.

Nó sử dụng lớp 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

Với Java 7, cách nhanh hơn để đi qua cây thư mục đã được giới thiệu với chức năng PathsFiles. Chúng nhanh hơn nhiều so với cách "cũ" File.

Đây sẽ là mã để đi qua và kiểm tra tên đường dẫn bằng một biểu thức chính quy:

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
Câu trả lời tuyệt vời :), cũng có một lớp được triển khai của nó được gọi là "SimpleFileVisitor", nếu bạn không cần tất cả các fucntions được triển khai, bạn chỉ có thể Ghi đè các chức năng cần thiết.
GalDude 33

13

Cách nhanh chóng để lấy nội dung của một thư mục bằng 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
Đẹp nhưng chỉ nhận được các tệp cho một thư mục. Nếu bạn muốn xem tất cả các thư mục con, hãy xem câu trả lời thay thế của tôi.
Dan

3
Files.newDirectoryStreamcó thể ném một IOException. Tôi khuyên bạn nên gói dòng đó trong câu lệnh try-with-Java7 để luồng sẽ luôn được đóng đối với bạn (ngoại lệ hoặc không, mà không cần câu lệnh a finally). Xem thêm tại đây: stackoverflow.com/questions/17739362/…
Greg

12

Giao diện của Java để đọc nội dung thư mục hệ thống tệp không hiệu quả lắm (như bạn đã phát hiện ra). JDK 7 khắc phục sự cố này bằng một giao diện hoàn toàn mới cho loại thứ này, sẽ mang lại hiệu suất ở mức nguyên bản cho các loại hoạt động này.

Vấn đề cốt lõi là Java thực hiện lệnh gọi hệ thống gốc cho mọi tệp đơn lẻ. Trên giao diện có độ trễ thấp, đây không phải là vấn đề lớn - nhưng trên một mạng có độ trễ vừa phải, nó thực sự tăng thêm. Nếu lập hồ sơ thuật toán của mình ở trên, bạn sẽ thấy rằng phần lớn thời gian được dành cho lệnh gọi isDirectory () khó chịu - đó là bởi vì bạn đang phải chịu một chuyến đi khứ hồi cho mỗi lệnh gọi đến isDirectory (). Hầu hết các hệ điều hành hiện đại đều có thể cung cấp loại thông tin này khi danh sách các tệp / thư mục được yêu cầu ban đầu (trái ngược với việc truy vấn từng đường dẫn tệp riêng lẻ cho các thuộc tính của nó).

Nếu bạn không thể đợi JDK7, một chiến lược để giải quyết độ trễ này là sử dụng đa luồng và sử dụng ExecutorService với số luồng tối đa để thực hiện đệ quy của bạn. Nó không tuyệt vời (bạn phải đối phó với việc khóa cấu trúc dữ liệu đầu ra của mình), nhưng nó sẽ nhanh hơn rất nhiều so với thực hiện một luồng đơn lẻ này.

Trong tất cả các cuộc thảo luận của bạn về loại điều này, tôi thực sự khuyên bạn nên so sánh với những gì tốt nhất bạn có thể làm bằng cách sử dụng mã gốc (hoặc thậm chí một tập lệnh dòng lệnh gần giống như vậy). Nói rằng mất một giờ để đi qua một cấu trúc mạng không thực sự có ý nghĩa nhiều. Nói với chúng tôi rằng bạn có thể làm điều đó ban đầu trong 7 giây, nhưng phải mất một giờ bằng Java sẽ thu hút sự chú ý của mọi người.


3
Java 7 hiện đã có nên một ví dụ về cách thực hiện nó trong Java 7 sẽ rất hữu ích. Hoặc ít nhất là một liên kết. Hoặc tên lớp để tìm kiếm trên google. - đây là «stackoverflow» chứ không phải «lý thuyết cs» ;-).
Martin

3
Hãy để chúng tôi xem ... Bài đăng ban đầu của tôi là vào tháng 3 năm 2010 ... Bây giờ là tháng 1 năm 2012 ... Và tôi vừa kiểm tra lịch sử kiểm kê thiết bị của mình, và tôi không thấy mình đã có cỗ máy thời gian vào tháng 3 năm 10, vì vậy tôi nghĩ rằng tôi có thể hợp lý khi trả lời mà không đưa ra ví dụ rõ ràng ;-)
Kevin Ngày


7

điều này sẽ hoạt động tốt ... và đệ quy của nó

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
Câu trả lời tốt nếu bạn muốn thứ gì đó hoạt động với java <7.
ssimm

3

Cá nhân tôi thích phiên bản FileUtils này. Dưới đây là một ví dụ tìm tất cả mp3 hoặc flac trong một thư mục hoặc bất kỳ thư mục con nào của nó:

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

3

Điều này sẽ hoạt động tốt

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


Chào mừng bạn đến với StackOverflow Mam's, bạn có thể làm rõ câu trả lời của bạn là cải tiến hay thay thế cho nhiều câu trả lời hiện có không?
Lilienthal

1

Hàm này có thể sẽ liệt kê tất cả tên tệp và đường dẫn của nó từ thư mục và các thư mục con của nó.

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
Ví dụ này không tính đến thực tế là phương thức listFiles (), có thể và sẽ trả về 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

có cảm giác như thật ngu ngốc khi truy cập vào hệ thống tệp và lấy nội dung cho mọi thư mục con thay vì lấy mọi thứ cùng một lúc.

Cảm giác của bạn là sai. Đó là cách hệ thống tập tin hoạt động. Không có cách nào nhanh hơn (ngoại trừ khi bạn phải làm điều này nhiều lần hoặc với các mẫu khác nhau, bạn có thể lưu vào bộ nhớ cache tất cả các đường dẫn tệp trong bộ nhớ, nhưng sau đó bạn phải đối phó với việc vô hiệu bộ nhớ cache, tức là điều gì sẽ xảy ra khi tệp được thêm / xóa / đổi tên trong khi ứng dụng đang chạy).


Vấn đề là tôi muốn tải tất cả các tệp thuộc một loại nhất định với một định dạng tên nhất định vào một thư viện được hiển thị cho người dùng và mỗi khi ứng dụng được khởi động, thư viện được cho là sẽ được cập nhật nhưng phải mất vĩnh viễn để cập nhật thư viện. Giải pháp duy nhất tôi có là chạy bản cập nhật trong nền nhưng vẫn khó chịu vì phải mất rất nhiều thời gian cho đến khi tất cả các tệp mới được tải. Phải có một cách tốt hơn để làm điều đó. Hoặc ít nhất là một cách tốt hơn để cập nhật cơ sở dữ liệu. Nó cảm thấy thật ngu ngốc khi xem qua tất cả các tệp mà nó đã trải qua quá trình xử lý. Có cách nào để chỉ tìm thấy các bản cập nhật nhanh chóng.
Hultner

@Hultner: Java 7 sẽ bao gồm một cơ sở để nhận thông báo về các bản cập nhật hệ thống tệp, nhưng điều đó sẽ vẫn chỉ hoạt động khi ứng dụng đang chạy, vì vậy trừ khi bạn muốn luôn có dịch vụ nền chạy, nó sẽ không hữu ích. Có thể có vấn đề đặc biệt với mạng chia sẻ như Kevin mô tả, nhưng miễn là bạn phụ thuộc vào việc quét qua toàn bộ cây thư mục, thực sự không có cách nào tốt hơn.
Michael Borgwardt

Có lẽ bạn có thể tạo một số tệp chỉ mục. Nếu có một cách để kiểm tra kích thước thư mục, bạn có thể chỉ cần quét các tệp mới khi kích thước thay đổi.
James P.

@James: không có cách nào để kiểm tra kích thước thư mục. Kích thước của một thư mục có được bằng cách lấy kích thước cho từng tệp và cộng chúng lại, trong tất cả các hệ thống tệp mà tôi biết. Trên thực tế, câu hỏi "kích thước của thư mục này là bao nhiêu?" thậm chí không thực sự có ý nghĩa gì nếu bạn xem xét các liên kết cứng.
Michael Borgwardt

Bạn đúng. Tôi vẫn cảm thấy rằng một số bộ nhớ đệm và / hoặc dấu vân tay có thể đẩy nhanh quá trình.
James P.

0

Mong bạn biết rằng isDirectory () là một phương thức khá chậm. Tôi thấy nó khá chậm trong trình duyệt tệp của mình. Tôi sẽ tìm kiếm một thư viện để thay thế nó bằng mã gốc.


0

Cách hiệu quả hơn mà tôi tìm thấy trong việc xử lý hàng triệu thư mục và tệp là nắm bắt danh sách thư mục thông qua lệnh DOS trong một số tệp và phân tích cú pháp. Khi bạn đã phân tích cú pháp dữ liệu thì bạn có thể thực hiện phân tích và tính toán thống kê.


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

Vui lòng thêm một số giải thích, quá.
d4Rk

0

Trong Guava, bạn không phải đợi Bộ sưu tập được trả lại cho mình mà thực sự có thể lặp lại các tệp. Có thể dễ dàng hình dung ra một IDoSomethingWithThisFilegiao diện trong chữ ký của hàm dưới đây:

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 cũng cho phép bạn chuyển đổi giữa các kiểu truyền tải khác nhau.


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

Một mã được tối ưu hóa khác

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

Xin vui lòng, bạn có thể mở rộng câu trả lời của bạn với giải thích chi tiết hơn? Điều này sẽ rất hữu ích cho sự hiểu biết. Cảm ơn bạn!
vezunchik

0

Thêm một ví dụ về liệt kê các tệp và thư mục bằng 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");
        }
}
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.