在Java中递归列出文件

我如何递归地列出在Java目录下的所有文件?框架是否提供任何实用程序?

我看到了很多俗气的实现。但没有来自框架或nio

364513 次浏览

我认为这应该是可行的:

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

这样你就有文件和dirs了。现在使用递归,并对dirs做同样的事情(File类有isDirectory()方法)。

FileUtilsiterateFileslistFiles方法。试一试吧。(从commons-io)

编辑:你可以检查在这里作为不同方法的基准。common -io方法似乎很慢,所以选择一些更快的从这里(如果重要的话)

你自己用简单的递归来写:

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

我的建议是:

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

System.out.println只是用来指示对该文件执行一些操作。没有必要区分文件和目录,因为一个正常的文件只会有零个子文件。

//准备运行

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

对于这种简单的转换,我更喜欢使用队列而不是递归:

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

具有单个列表的非递归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);
}
}

基于堆栈的答案。这里有一个在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>

Java 8提供了一个很好的流来处理树中的所有文件。

try (Stream<Path> stream = Files.walk(Paths.get(path))) {
stream.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项目

在Java 8中,我们现在可以使用Files实用程序遍历文件树。非常简单。

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

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

我的版本(当然我可以在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;
}

您可以使用下面的代码来递归地获得特定文件夹或目录的文件列表。

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

示例输出*.csv文件在目录递归搜索子目录使用files .find()从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);
}


}

发布这个例子,因为我有困难理解如何在布莱恩给出的#1个例子中传递文件名参数,使用foreach对流结果-

希望这能有所帮助。

这段代码可以运行了

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

这里有一个使用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);
}
}
}

我想出了这个来递归打印所有文件/文件名。

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

Kotlin有FileTreeWalk用于此目的。例如:

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

将生成给定根下所有非目录文件的文本列表,每行一个文件,其路径相对于根和长度。

接受的答案很好,但是当你想在lambda中执行IO时,它就崩溃了。

如果你的action声明了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"]等。

列出所有提供扩展名的文件,带有扫描选项 子文件夹(递归)< / p >

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

根据@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);
}
}
}
}
List<Path> filePaths = Files
.find(Paths.get(dir), Integer.MAX_VALUE, (filePath, fileAttr) -> fileAttr.isRegularFile() || fileAttr.isDirectory())
.collect(Collectors.toList());

filePaths将有文件和文件夹列表,可以迭代并进一步进行。