Streams是惰性的,因此以下语句不会加载path内存引用的目录的整个子节点; 相反,它逐个加载它们,并且在每次调用之后forEach,引用的目录p都有资格进行垃圾收集,因此它的文件描述符也应该关闭:
Files.list(path).forEach(p ->
absoluteFileNameQueue.add(
p.toAbsolutePath().toString()
)
);
Run Code Online (Sandbox Code Playgroud)
基于这个假设,我实现了广度优先的文件遍历工具:
public class FileSystemTraverser {
public void traverse(String path) throws IOException {
traverse(Paths.get(path));
}
public void traverse(Path root) throws IOException {
final Queue<String> absoluteFileNameQueue = new ArrayDeque<>();
absoluteFileNameQueue.add(root.toAbsolutePath().toString());
int maxSize = 0;
int count = 0;
while (!absoluteFileNameQueue.isEmpty()) {
maxSize = max(maxSize, absoluteFileNameQueue.size());
count += 1;
Path path = Paths.get(absoluteFileNameQueue.poll());
if (Files.isDirectory(path)) {
Files.list(path).forEach(p ->
absoluteFileNameQueue.add(
p.toAbsolutePath().toString()
)
);
}
if (count % 10_000 == 0) {
System.out.println("maxSize = " + maxSize);
System.out.println("count = " + count);
}
}
System.out.println("maxSize = " + maxSize);
System.out.println("count = " + count);
}
}
Run Code Online (Sandbox Code Playgroud)
我以一种相当直接的方式使用它:
public class App {
public static void main(String[] args) throws IOException {
FileSystemTraverser traverser = new FileSystemTraverser();
traverser.traverse("/media/Backup");
}
}
Run Code Online (Sandbox Code Playgroud)
安装的磁盘/media/Backup有大约300万个文件.
由于某种原因,大约140,000标记,程序崩溃与此堆栈跟踪:
Exception in thread "main" java.nio.file.FileSystemException: /media/Backup/Disk Images/Library/Containers/com.apple.photos.VideoConversionService/Data/Documents: Too many open files
at sun.nio.fs.UnixException.translateToIOException(UnixException.java:91)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107)
at sun.nio.fs.UnixFileSystemProvider.newDirectoryStream(UnixFileSystemProvider.java:427)
at java.nio.file.Files.newDirectoryStream(Files.java:457)
at java.nio.file.Files.list(Files.java:3451)
Run Code Online (Sandbox Code Playgroud)
在我看来,由于某种原因文件描述符没有被关闭或Path对象不是垃圾收集导致应用程序最终崩溃.
任何想法我在这里缺少什么,我如何解决这个问题(没有诉诸java.io.File::list(即通过留在NIO2和Paths 的令人满意)?
我怀疑JVM是否保持文件描述符处于打开状态.我把这个堆转储带到120,000个文件标记周围:
我在VisualVM中安装了一个文件描述符探测插件,实际上它显示FD没有被处理掉(正如cerebrotecnologico和k5正确指出的那样):
好像从Files.list(Path)返回的Stream没有正确关闭.另外,你不应该在流上使用forEach,你不确定它不是并行的(因此.sequential()).
try (Stream<Path> stream = Files.list(path)) {
stream.map(p -> p.toAbsolutePath().toString()).sequential().forEach(absoluteFileNameQueue::add);
}
Run Code Online (Sandbox Code Playgroud)
从Java文档:
"返回的流封装了DirectoryStream.如果需要及时处理文件系统资源,则应使用try-with-resources构造来确保在流操作完成后调用流的close方法"
| 归档时间: |
|
| 查看次数: |
2391 次 |
| 最近记录: |