在JAVA中遍歷目錄下的所有文件,很多人第一個想到的是用File.listFiles方法遞歸. 然而這并不是最好的方法, 這里跟幾個其他的方法做一個對比.
- 自己寫函數遞歸
- google的guava工具包
- common io工具包
- jdk自帶的nio Files工具類
package test.walkfile; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collection; import java.util.concurrent.ConcurrentSkipListSet; import org.Apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.TrueFileFilter; import com.google.common.io.Files; import util.TimeUtil; public class WalkFile { private static final String rootPath = "D:\"; public static void main(String[] args) throws IOException { Collection<File> res = new ConcurrentSkipListSet<>(); File root = new File(rootPath); TimeUtil.timeStart("byFunction"); byFunction(root, res); System.out.println(res.size()); TimeUtil.timeEnd("byFunction"); res.clear(); TimeUtil.timeStart("guavaBreadthFirst"); guavaBreadthFirst(root, res); System.out.println(res.size()); TimeUtil.timeEnd("guavaBreadthFirst"); res.clear(); TimeUtil.timeStart("guavaDepthFirst"); guavaDepthFirst(root, res); System.out.println(res.size()); TimeUtil.timeEnd("guavaDepthFirst"); res.clear(); TimeUtil.timeStart("byCommonio"); byCommonio(root, res); System.out.println(res.size()); TimeUtil.timeEnd("byCommonio"); res.clear(); TimeUtil.timeStart("byJdkFiles"); byJdkFiles(root, res); System.out.println(res.size()); TimeUtil.timeEnd("byJdkFiles"); res.clear(); } /** * 自己寫算法遍歷文件. * * @param root * 根路徑 * @param allFiles * 遍歷結果集合 */ private static void byFunction(File root, Collection<File> allFiles) { if (root == null || !root.exists()) { return; } if (root.isFile()) { allFiles.add(root); } else { File[] listFiles = root.listFiles(); if (listFiles != null) { for (File childFile : listFiles) { byFunction(childFile, allFiles); } } } } /** * guava 廣度優先遍歷文件. * * @param root * 根路徑 * @param allFiles * 遍歷結果集合 */ private static void guavaBreadthFirst(File root, Collection<File> allFiles) { Iterable<File> files = Files.fileTraverser().breadthFirst(root); files.forEach(f -> allFiles.add(f)); } /** * guava 深度優先遍歷文件. * * @param root * 根路徑 * @param allFiles * 遍歷結果集合 */ private static void guavaDepthFirst(File root, Collection<File> allFiles) { Iterable<File> files = Files.fileTraverser().depthFirstPostOrder(root); files.forEach(f -> allFiles.add(f)); } /** * 通過common io工具包遍歷文件. * * @param root * 根路徑 * @param allFiles * 遍歷結果集合 */ private static void byCommonio(File root, Collection<File> allFiles) { allFiles.addAll(FileUtils.listFiles(root, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)); } /** * 通過jdk的nio Files工具類遍歷文件. * * @param root * 根路徑 * @param allFiles * 遍歷結果集合 * @throws IOException * IO異常 */ private static void byJdkFiles(File root, Collection<File> allFiles) throws IOException { java.nio.file.Files.walkFileTree(root.toPath(), new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { allFiles.add(file.toFile()); return super.visitFile(file, attrs); } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); } }
程序執行結果如下:
152058 byFunction:6584 ms 178667 guavaBreadthFirst:5539 ms 178667 guavaDepthFirst:5153 ms 152058 byCommonio:6543 ms 152058 byJdkFiles:2272 ms
執行3次結果統計:
3次執行時間
jdk自帶的Files類比自己遞歸節約了: (6443 - 2483)÷6443 = 61%的時間. 有些情況下甚至可以節約80%以上的時間. 所以還有什么理由再去自己寫算法呢?
除了遍歷文件,還有復制文件也是如此. jdk nio的Files工具類用時大概是普通buffer數組方式的1/3, FileChannel和Files差不多一樣快.
當性能想要大幅提高的時候,先去java類庫找有沒有合適的工具類.如果沒有再考慮其他jar包. 另外發現,common的jar包一般性能不是很好, 可能偏重于可靠性, google的工具包要好一些.