用Javarecursion列出目录中的所有文件
我有这个函数recursion地打印目录中的所有文件的名称。 问题是我的代码非常慢,因为每次迭代都必须访问远程networking设备。
我的计划是首先recursion地加载目录中的所有文件,然后使用正则expression式遍历所有文件,以过滤掉所有不需要的文件。 有没有人有更好的build议?
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()); } } }
这只是一个稍后的testing,我不打算使用这样的代码,而是要将每个匹配高级正则expression式的文件的path和修改date添加到数组中。
假设这是您正在编写的实际生产代码,那么我build议您使用解决scheme来解决已经解决的问题 – Apache Commons IO ,特别是FileUtils.listFiles()
。 它处理嵌套的目录,filter(基于名称,修改时间等)。
例如,对于你的正则expression式:
Collection files = FileUtils.listFiles( dir, new RegexFileFilter("^(.*?)"), DirectoryFileFilter.DIRECTORY );
这将recursionsearch与^(.*?)
正则expression式匹配的文件,将结果作为集合返回。
值得注意的是,这不会比滚动你自己的代码更快,它是做同样的事情 – 用Java拖网文件系统只是慢。 不同的是,Apache Commons版本里面没有任何bug。
在Java 8中,它是一个通过Files.find()
具有任意大的深度(例如999
)和BasicFileAttributes
( isRegularFile()
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
这是一个非常简单的recursion方法来从给定的根获取所有文件。
它使用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
function引入了一种更快的方式来通过目录树。 它们比“旧” File
方式快得多。
这将是用正则expression式遍历并检查path名的代码:
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接口不是非常高性能的(就像你发现的那样)。 JDK 7通过一个全新的界面来解决这个问题,它可以为这些操作带来本地的性能。
核心问题是Java对每个文件都进行本地系统调用。 在低延迟的接口上,这并不是什么大不了的事情,但是在一个延迟适中的networking上,它确实会加起来。 如果你在上面描述你的algorithm,你会发现大部分的时间都花在讨厌的isDirectory()调用中 – 这是因为你每次调用isDirectory()都会产生一个往返。 当最初请求文件/文件夹列表(而不是查询每个文件path的属性)时,大多数现代操作系统都可以提供这种信息。
如果您不能等待JDK7,则解决此延迟的一个策略是使用multithreading,并使用具有最大线程数的ExecutorService来执行recursion。 这不是很好(你必须处理你的输出数据结构的locking),但这比单线程快很多。
在所有关于这类事情的讨论中,我强烈build议您使用本机代码(甚至是大致相同的命令行脚本)进行比较。 说遍历一个networking结构需要一个小时的时间并不意味着太多。 告诉我们你可以在7秒钟内完成本地化,但是在Java中需要一个小时才能吸引人们的注意力。
使用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();
这将工作得很好…和它的recursion
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 } } }
我个人喜欢这个版本的FileUtils。 下面是一个例子,可以find目录或其任何子目录中的所有mp3或flacs:
String[] types = {"mp3", "flac"}; Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true);
这个函数可能会列出其目录及其子目录中的所有文件名及其path。
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); } }
这将工作正常
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); } } }
感觉像是愚蠢的访问文件系统,并获取每个子目录的内容,而不是一次获取所有内容。
你的感觉是错误的。 这就是文件系统的工作原理。 没有更快的方法(除非必须重复执行此操作或针对不同模式,则可以将所有文件pathcaching到内存中,但是必须处理caching失效,即在添加/删除/重命名文件时会发生什么情况该应用程序运行)。
就这样你知道isDirectory()是一个相当缓慢的方法。 我在文件浏览器中发现速度很慢。 我将查找一个库,用本机代码replace它。
我在处理数百万个文件夹和文件时遇到的更有效的方法是通过DOS命令在某个文件中捕获目录列表并parsing它。 一旦你parsing了数据,那么你可以做分析和计算统计。
在番石榴你不必等待一个集合返回给你,但实际上可以遍历文件。 在下面的函数的签名中很容易想象一个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也允许你在各种遍历样式之间。