原文:http://www.yi-dao.com/works/model/sample/findFiles.htm 遍历深层次的树状结构。这里的例子是查找所有本地硬盘中的“java.exe”文件。 当向一个深层的目录寻找特定的文件时,很容易想到通过不断地迭代来遍历。
import java.io.File;
public class FileSearchTool { public static void main(String[] args) { String toFind = "java.exe"; File[] roots = File.listRoots(); for (int i = 0; i < roots.length; i++) { searchList(roots[i], toFind); } }
public static void searchList(File file, String toFind) { File[] files = file.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { searchList(file, toFind); } else if (files[i].getName().equals(toFind)) { System.out.println(files[i].getAbsolutePath()); } } } } }
上面的代码表面上看好象可以完成任务,可上面的方法却是无效的。上面忽略了目录的深度与广度,在递归的过程中不断地分配资源(File[] files = file.listFiles();),而只有当更深层的递归结束时才能结束当前的工作并释放这些资源,这样极容易导致堆栈溢出和 JVM 停止工作。 为了解决这样的问题,应该尽量避免使用递归。不用递归也不分配大量资源的方法才是有效的方法。
下面是不用递归时查找硬盘中的 "java.exe" 的程序。分析这个过程的状态机请见:http://www.yi-dao.com/works/model/sample/findFiles.ydm 下载这个文档,使用“易道模型”打开,易道模型下载:http://www.sharebank.com.cn/soft/soft_view.php?id=11135 -------------------------------------------------------
public class Test{ public static void main(String[] args) { File[] roots = File.listRoots(); String nameToFind = "java.exe"; for (int i = 0; i < roots.length; i++) { File[] files = findFiles(roots[i], nameToFind); for (int j = 0; j < files.length; j++) { System.out.println(files[j].getAbsolutePath()); } } }
public static File[] findFiles(File dir, String nameToFind) { Stack curPath = new Stack(); curPath.push(dir); return findFiles(curPath, nameToFind); }
public static final int FIND_SUB = 0; // 找子节点 public static final int FIND_SIB = 1; // 找同级节点 public static final int FIND_END = 2; // 结束 public static File[] findFiles(Stack curPath, String nameToFind) { /** 只检测目录 */ class MyDirFilter implements FileFilter { public boolean accept(File pathname) { return (pathname != null) && pathname.isDirectory(); } }
/** 只检测文件名相同 */ class MyFileFilter implements FileFilter { String toFind; MyFileFilter(String toFind) { this.toFind = toFind; }
public boolean accept(File pathname) { return (pathname != null) && pathname.isFile() && toFind.equalsIgnoreCase(pathname.getName()); } }
MyFileFilter fileFilter = new MyFileFilter(nameToFind); MyDirFilter dirFilter = new MyDirFilter(); int state = FIND_SUB; // 开始 LinkedHashSet found = new LinkedHashSet(); while (state != FIND_END) { File dir = (File) curPath.pop(); // 当前目录 // System.out.println(dir.getAbsolutePath()); if (state == FIND_SUB) { // 查找子节点 File[] subDirs = dir.listFiles(dirFilter); if (subDirs == null || subDirs.length == 0) { // 没有子节点 curPath.push(dir); state = FIND_SIB; // 下一次需要找同级节点 } else { curPath.push(dir); curPath.push(subDirs[0]); state = FIND_SUB; } } else if (state == FIND_SIB) { // 查找同级节点 File[] files = dir.listFiles(fileFilter); if (files != null) { for (int i = 0; i < files.length; i++) { found.add(files[i]); } }
if (curPath.isEmpty()) { state = FIND_END; // 已经没有可以找的了,需要退出查找过程 } else { File parentDir = (File) curPath.peek(); File[] sibDirs = parentDir.listFiles(dirFilter); for (int i = 0; i < sibDirs.length; i++) { if (dir.equals(sibDirs[i])) { // 找到了当前的位置 if (i + 1 < sibDirs.length) { // 存在下一个同级节点 curPath.push(sibDirs[i + 1]); state = FIND_SUB; // 需要查找子节点 } else { // 这就是最后一个同级节点 state = FIND_SIB; } break; } } } } } return (File[]) found.toArray(new File[found.size()]); } }
上面的方法在整个文件目录的遍历过程中只使用了一个 LinkedHashSet 来记录当前的路径信息,并没有在向更深层次遍历时大量开销过多的资源,所以JVM是可以承受的。这样的方法才有效。

|