![](/img/trans.png)
[英]Problems with “Config.Msi” directory while trying to recursively list all files from system root using java 8
[英]List all files from a directory recursively with Java
我有这个 function 递归地打印目录中所有文件的名称。 问题是我的代码非常慢,因为它必须在每次迭代时访问 remote.network 设备。
我的计划是首先从目录中递归加载所有文件,然后 go 使用正则表达式过滤掉所有我不需要的文件。 有更好的解决方案吗?
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());
}
}
}
这只是一个测试。 稍后我不会使用这样的代码; 相反,我将把与高级正则表达式匹配的每个文件的路径和修改日期添加到数组中。
假设这是您将要编写的实际生产代码,那么我建议使用解决方案来解决这种已经解决的问题 - Apache Commons IO ,特别是FileUtils.listFiles()
。 它处理嵌套目录、过滤器(基于名称、修改时间等)。
例如,对于您的正则表达式:
Collection files = FileUtils.listFiles(
dir,
new RegexFileFilter("^(.*?)"),
DirectoryFileFilter.DIRECTORY
);
这将递归搜索与^(.*?)
正则表达式匹配的文件,并将结果作为集合返回。
值得注意的是,这不会比滚动您自己的代码快,它在做同样的事情 - 在 Java 中拖网文件系统只是很慢。 不同的是,Apache Commons 版本不会有任何错误。
在 Java 8 中,它是通过Files.find()
具有任意大深度(例如999
)和isRegularFile()
BasicFileAttributes
的 1-liner
public static printFnames(String sDir) {
Files.find(Paths.get(sDir), 999, (p, bfa) -> bfa.isRegularFile()).forEach(System.out::println);
}
要添加更多过滤,请增强 lambda,例如在过去 24 小时内修改的所有 jpg 文件:
(p, bfa) -> bfa.isRegularFile()
&& p.getFileName().toString().matches(".*\\.jpg")
&& bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000
这是一种非常简单的递归方法,可以从给定的根目录中获取所有文件。
它使用 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;
}
在 Java 7 中,通过Paths
和Files
功能引入了一种更快的遍历目录树的方法。 它们比“旧” File
方式快得多。
这将是使用正则表达式遍历和检查路径名称的代码:
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;
}
});
}
使用 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();
Java 的用于读取文件系统文件夹内容的接口的性能不是很好(如您所见)。 JDK 7 用一个全新的接口修复了这个问题,它应该为这些类型的操作带来本机级别的性能。
核心问题是 Java 对每个文件都进行本地系统调用。 在低延迟接口上,这没什么大不了的——但在延迟适中的网络上,它确实加起来了。 如果您在上面分析您的算法,您会发现大部分时间都花在讨厌的 isDirectory() 调用上——这是因为每次调用 isDirectory() 都会导致往返。 大多数现代操作系统可以在最初请求文件/文件夹列表时提供此类信息(而不是查询每个单独的文件路径的属性)。
如果您迫不及待地等待 JDK7,那么解决此延迟的一种策略是采用多线程并使用具有最大线程数的 ExecutorService 来执行递归。 这不是很好(你必须处理输出数据结构的锁定),但它会比做这个单线程快很多。
在关于此类事情的所有讨论中,我强烈建议您与使用本机代码(甚至是执行大致相同操作的命令行脚本)所能做到的最好的情况进行比较。 说遍历一个网络结构需要一个小时并没有多大意义。 告诉我们你可以在 7 秒内完成原生,但在 Java 中需要一个小时才会引起人们的注意。
这将工作得很好......而且它的递归
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
}
}
}
这将正常工作
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);
}
}
}
我个人喜欢这个版本的 FileUtils。 这是一个在目录或其任何子目录中查找所有 mp3 或 flac 的示例:
String[] types = {"mp3", "flac"};
Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true);
爪哇 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);
}
}
此函数可能会列出其目录及其子目录中的所有文件名及其路径。
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]);
}
}
}
感觉访问文件系统并获取每个子目录的内容而不是一次获取所有内容是愚蠢的。
你的感觉是错误的。 这就是文件系统的工作方式。 没有更快的方法(除非您必须重复执行此操作或针对不同模式执行此操作,您可以将所有文件路径缓存在内存中,但是您必须处理缓存失效,即在添加/删除/重命名文件时会发生什么应用程序运行)。
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);
}
}
在 Guava 中,您不必等待 Collection 返回给您,但实际上可以遍历文件。 很容易想象以下函数的签名中有一个IDoSomethingWithThisFile
接口:
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还允许您在各种遍历样式之间进行。
只是让您知道 isDirectory() 是一种非常慢的方法。 我发现它在我的文件浏览器中很慢。 我将研究一个库以用本机代码替换它。
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());
}
}
另一个优化的代码
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());
}
}
使用 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");
}
}
我在 Windows 11 上用 284 个文件夹中的 60,000 个文件测试了一些方法:
public class App {
public static void main(String[] args) throws Exception {
Path path = Paths.get("E:\\书籍");
// 1.walkFileTree
long start1 = System.currentTimeMillis();
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
// if(pathMatcher.matches(file))
// files.add(file.toFile());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
// System.out.println(dir.getFileName());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException e) {
return FileVisitResult.CONTINUE;
}
});
long end1 = System.currentTimeMillis();
// 2. newDirectoryStream
long start2 = System.currentTimeMillis();
search(path.toFile());
long end2 = System.currentTimeMillis();
// 3. listFiles
long start3 = System.currentTimeMillis();
getFileNames(path);
long end3 = System.currentTimeMillis();
System.out.println("\r执行耗时:" + (end1 - start1));
System.out.println("\r执行耗时:" + (end2 - start2));
System.out.println("\r执行耗时:" + (end3 - start3));
}
private static void getFileNames(Path dir) {
try(DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path path : stream) {
if(Files.isDirectory(path)) {
getFileNames(path);
}
}
} catch(IOException e) {
e.printStackTrace();
}
}
public static void search(File file) {
Queue<File> q = new LinkedList<>();
q.offer(file);
while (!q.isEmpty()) {
try {
for (File childfile : q.poll().listFiles()) {
// System.out.println(childfile.getName());
if (childfile.isDirectory()) {
q.offer(childfile);
}
}
} catch (Exception e) {
}
}
}
}
遍历文件树 | 列表文件 | 新目录流 |
---|---|---|
68 | 451 | 493 |
64 | 464 | 482 |
61 | 478 | 457 |
67 | 477 | 488 |
59 | 474 | 466 |
已知性能问题:
来自凯文戴的回答:
如果您分析上面的算法,您会发现大部分时间花在了烦人的 isDirectory() 调用上——那是因为每次调用 isDirectory() 都会招致一次往返。
listfiles() 将为每个条目创建新文件 Object
我发现在处理数百万个文件夹和文件时更有效的方法是通过 DOS 命令在某个文件中捕获目录列表并对其进行解析。 一旦你解析了数据,你就可以进行分析和计算统计数据。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.