深层次树状结构遍历的改进 查找硬盘中的 “java.exe”

来源:互联网 发布:解压缩数据错误 编辑:程序博客网 时间:2024/05/22 08:25

原文: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是可以承受的。这样的方法才有效。

原创粉丝点击