迭代文件列表的有效方法

时间:2012-03-28 04:19:39

标签: java

我正在寻找一种有效的方法来迭代一个或多个目录中的数千个文件。

迭代目录中文件的唯一方法似乎是File.list*()个函数。这些函数有效地加载某种Collection中的整个文件列表,然后让用户迭代它。就时间/内存消耗而言,这似乎是不切实际的。我试着看看commons-io和其他类似的工具。但他们最终都在内部的某处调用了File.list*()。 JDK7的walkFileTree()接近,但我无法控制何时选择下一个元素。

我在目录中有超过150,000个文件,经过多次-Xms / -Xmm试运行后,我摆脱了内存溢出问题。但是填充阵列所需的时间并没有改变。

我希望创建一种Iterable类,它使用opendir()/ closedir()函数来根据需要延迟加载文件名。有没有办法做到这一点?

更新

Java 7 NIO.2支持通过java.nio.file.DirectoryStream进行文件迭代。这是一个Iterable类。对于JDK6及更低版本,唯一的选择是File.list*()方法。

4 个答案:

答案 0 :(得分:3)

这是一个如何迭代目录条目而不必将其中的159k存储在数组中的示例。根据需要添加错误/异常/关闭/超时处理。此技术使用辅助线程加载小阻塞队列。

用法是:

FileWalker z = new FileWalker(new File("\\"), 1024); // start path, queue size
Iterator<Path> i = z.iterator();
while (i.hasNext()) {
  Path p = i.next();
}

示例:

public class FileWalker implements Iterator<Path> {
  final BlockingQueue<Path> bq;
  FileWalker(final File fileStart, final int size) throws Exception {
  bq = new ArrayBlockingQueue<Path>(size);
  Thread thread = new Thread(new Runnable() {
    public void run() {
      try {
        Files.walkFileTree(fileStart.toPath(), new FileVisitor<Path>() {
          public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            return FileVisitResult.CONTINUE;
          }
          public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            try {
              bq.offer(file, 4242, TimeUnit.HOURS);
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
            return FileVisitResult.CONTINUE;
          }
          public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            return FileVisitResult.CONTINUE;
          }
          public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            return FileVisitResult.CONTINUE;
          }
        });
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  });
  thread.setDaemon(true);
  thread.start();
  thread.join(200);
}
public Iterator<Path> iterator() {
  return this;
}
public boolean hasNext() {
  boolean hasNext = false;
  long dropDeadMS = System.currentTimeMillis() + 2000;
  while (System.currentTimeMillis() < dropDeadMS) {
    if (bq.peek() != null) {
      hasNext = true;
      break;
    }
    try {
      Thread.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  return hasNext;
}
public Path next() {
  Path path = null;
  try {
    path = bq.take();
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  return path;
}
public void remove() {
  throw new UnsupportedOperationException();
}
}

答案 1 :(得分:1)

  

这在时间/内存消耗方面似乎是不切实际的。

即使是150,000个文件也不会消耗不切实际的内存量。

  

我希望创建一种Iterable类,它使用opendir()/ closedir()函数来根据需要延迟加载文件名。有没有办法做到这一点?

您需要编写或查找本机代码库才能访问这些C函数。它可能会引入比它解决的问题更多的问题。我的建议是只使用File.list()并增加堆大小。


实际上,还有另一个hacky替代方案。使用System.exec运行ls命令(或等效的Windows)并编写迭代器以读取和解析命令输出文本。这避免了与使用Java的本机库相关的麻烦。

答案 2 :(得分:0)

您可以按文件类型对装载进行分组以缩小批次范围吗?

答案 3 :(得分:0)

我只是想知道为什么一个普通的file.list()方法返回文件名的String [](而不是file.listFiles())消耗大量内存?它是一个本机调用,它只返回文件名。 可能你可以迭代它并懒惰地加载你需要的任何文件对象。