簡體   English   中英

"遞歸列出Java中的文件"

[英]Recursively list files in Java

如何遞歸列出Java目錄下的所有文件? 該框架是否提供任何實用程序?

我看到了很多 hacky 的實現。 但是沒有來自框架或nio<\/a>

Java 8 提供了一個很好的流來處理樹中的所有文件。

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

這提供了一種自然的方式來遍歷文件。 由於它是一個流,您可以對結果執行所有不錯的流操作,例如限制、分組、映射、提前退出等。

更新:我可能會指出還有Files.find它需要一個BiPredicate ,如果您需要檢查文件屬性,它可能會更有效。

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

請注意,雖然 JavaDoc 沒有說明此方法可能比Files.walk更有效, 實際上是相同的,如果您還在過濾器中檢索文件屬性,則可以觀察到性能差異。 最后,如果你需要過濾屬性使用Files.find ,否則使用Files.walk ,主要是因為有重載而且更方便。

測試:根據要求,我提供了許多答案的性能比較。 查看包含結果和測試用例Github 項目

文件實用程序iterateFileslistFiles方法。 給他們一個嘗試。 (來自commons-io

編輯:您可以在此處查看不同方法的基准。 似乎 commons-io 方法很慢,所以從這里選擇一些更快的方法(如果重要的話)

// 准備運行

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

}

Java 7 將有 Files.walkFileTree

如果您提供起點和文件訪問者,它將在文件訪問者遍歷文件樹中的文件時調用文件訪問者的各種方法。 我們希望人們在開發遞歸復制、遞歸移動、遞歸刪除或設置權限或對每個文件執行其他操作的遞歸操作時使用它。

現在有 關於這個問題的完整Oracle 教程

不需要外部庫。
返回一個集合,這樣你就可以在調用后對它做任何你想做的事情。

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

我會選擇類似的東西:

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

System.out.println 只是用於指示對文件執行某些操作。 不需要區分文件和目錄,因為普通文件只會有零個子文件。

對於這種簡單的遍歷,我更喜歡使用隊列而不是遞歸:

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

只需使用簡單的遞歸自己編寫:

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

使用 Java 7,您可以使用以下類:

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

我認為這應該可以完成工作:

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

這樣你就有了文件和目錄。 現在使用遞歸並對 dirs 執行相同的操作( File類具有isDirectory()方法)。

在 Java 8 中,我們現在可以使用 Files 實用程序來遍歷文件樹。 很簡單。

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

此代碼已准備好運行

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

除了遞歸遍歷之外,還可以使用基於訪問者的方法。

下面的代碼是使用基於訪問者的方法進行遍歷。預計程序的輸入是要遍歷的根目錄。

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

您可以使用以下代碼遞歸獲取特定文件夾或目錄的文件列表。

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

我想出了這個來遞歸打印所有文件/文件名。

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

列出具有提供的擴展名的所有文件,可選擇掃描子文件夾(遞歸)

 public static ArrayList<File> listFileTree(File dir,boolean recursive) {
        if (null == dir || !dir.isDirectory()) {
            return new ArrayList<>();
        }
        final Set<File> fileTree = new HashSet<File>();
        FileFilter fileFilter = new FileFilter() {
            private final String[] acceptedExtensions = new String[]{"jpg", "png", "webp", "jpeg"};

            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    return true;
                }
                for (String extension : acceptedExtensions) {
                    if (file.getName().toLowerCase().endsWith(extension)) {
                        return true;
                    }
                }
                return false;
            }
        };
        File[] listed = dir.listFiles(fileFilter);
        if(listed!=null){
            for (File entry : listed) {
                if (entry.isFile()) {
                    fileTree.add(entry);
                } else if(recursive){
                    fileTree.addAll(listFileTree(entry,true));
                }
            }
        }
        return new ArrayList<>(fileTree);
    }

具有單個列表的非遞歸 BFS(特定示例是搜索 *.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);
        }
    }

我的版本(當然我可以使用 Java 8 中的內置 walk ;-)):

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

這是一個使用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());
        }
    }
}
    private void fillFilesRecursively(File file, List<File> resultFiles) {
        if (file.isFile()) {
            resultFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                fillFilesRecursively(child, resultFiles);
            }
        }
    }

為此,Kotlin 有FileTreeWalk 例如:

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

將生成給定根目錄下所有非目錄文件的文本列表,每行一個文件,其中包含相對於根目錄的路徑和長度。

接受的答案很好,但是當您想在 lambda 中執行 IO 時它會崩潰。

如果您的操作聲明了 IOExceptions,您可以執行以下操作。

您可以將過濾后的流視為Iterable ,然后在常規 for-each 循環中執行您的操作。 這樣,您就不必在 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);
    }
}

在這里找到這個技巧: https : //stackoverflow.com/a/32668807/1207791

即使有人已經提供 Java 8 walk,您也可以采用另一種方法。

這將遞歸地為您提供所有文件

  private Stream<File> files(File file) {
    return file.isDirectory()
            ? Arrays.stream(file.listFiles()).flatMap(this::files)
            : Stream.of(file);
}
public static String getExten(String path) {
    int i = path.lastIndexOf('.');
    if (i > 0) {
       return path.substring(i);
    }
    else return "";
}
public static List<String> GetAllFiles(String path, List<String>fileList){
    File file = new File(path);
    
    File[] files = file.listFiles();
    for(File folder:files) {
        if(extensions.contains(getExten(folder.getPath()))) {
            fileList.add(folder.getPath());
        }
    }
    File[] direcs = file.listFiles(File::isDirectory);
    for(File dir:direcs) {
        GetAllFiles(dir.getPath(),fileList);
    }
    return fileList;
    
}

這是一個簡單的遞歸函數,應該為您提供所有文件。 extensions 是一個字符串列表,只包含那些被接受的擴展名。 示例擴展名 = [".txt",".docx"] 等。

示例使用 java.nio 中的 Files.find() 在目錄遞歸搜索子目錄中輸出 *.csv 文件:

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

    }

發布此示例,因為我無法理解如何在 Bryan 給出的 #1 示例中傳遞文件名參數,在 Stream-result 上使用 foreach -

希望這可以幫助。

根據@Michael 的回答,添加檢查 listFiles 是否返回 null

static Stream<File> files(File file) {
    return file.isDirectory()
            ? Optional.ofNullable(file.listFiles()).map(Stream::of).orElseGet(Stream::empty).flatMap(MainActivity::files)
            : Stream.of(file);
}

或使用Lightweight-Stream-API ,它支持 Android5 & Android6

static Stream<File> files(File f) {
    return f.isDirectory() ? Stream.ofNullable(f.listFiles()).flatMap(MainActivity::files) : Stream.of(f);
}

接受的答案很差,因為它可能導致資源泄漏。

Files.walkDirectoryStreams支持。

返回的流封裝了一個或多個 DirectoryStream。 如果需要及時處理文件系統資源,則應使用 try-with-resources 構造以確保在流操作完成后調用流的 close 方法。 在關閉的流上操作將導致 IllegalStateException。

DirectoryStream 必須按照其 javadoc 中的規定關閉:

DirectoryStream 在創建時打開,並通過調用 close 方法關閉。 關閉目錄流會釋放與該流相關聯的任何資源。 未能關閉流可能會導致資源泄漏。 try-with-resources 語句提供了一個有用的構造來確保流關閉:

Path dir = ...
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
    for (Path entry: stream) {
        ...
    }
}

結果,真正的答案是:

try (Stream<Path> stream = Files.walk(Paths.get(path))) {
    // Do something with the stream.
    stream.filter(Files::isRegularFile)
          .forEach(System.out::println);
}
import java.io.File;

public class Main {
    public static void main(String[] args) {
        loopFiles(new File("C:\\Users\\serge.klimkovitch\\Documents"));
    }

    private static void loopFiles(File element) {
        if (element.isDirectory()) {
            for (File currentFile : element.listFiles()) {
                loopFiles(currentFile);
                System.out.println(currentFile);
            }
        }
    }
}

列出 filePaths = Files.find(Paths.get(dir),Integer.MAX_VALUE,(filePath, fileAttr) -> fileAttr.isRegularFile() || fileAttr.isDirectory()).collect(Collectors.toList());

filePaths -> 將有文件和文件夾列表,可以迭代並繼續進行

基於堆垛機答案。 這是一個在 JSP 中工作的解決方案,無需任何外部庫,因此您幾乎可以將它放在服務器上的任何位置:

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

然后你只需執行以下操作:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM