在 O(1) 中迭代 scala/java 中的文件 打开文件描述符

2022-09-03 15:47:05

看起来nio's返回一个流,当使用时,每个迭代的文件保持一个文件描述符,直到在整个流上被调用。这意味着具有 1,000 个以上文件的数据目录可以轻松刷取公共值。此文件描述符累积的总体效果在处理嵌套遍历时会进一步加剧。.list.closeulimit

除了生成对 OS 文件列表命令的调用之外,还有什么替代方法来循环访问大型目录的文件?如果迭代大型目录的文件,则只会根据当前迭代的文件维护文件描述符,这将很酷,正如正确的流语义所暗示的那样。

编辑:

list返回一个 java 流 哪个 api 调用将用于在处理流中的每个项目后关闭它,而不是仅在整个流被关闭时,以实现更精简的迭代?在scala中,这可以使用来自更好文件的api包装器轻松调整,从这里开始。java.nio.file.Path


答案 1

如果发生这种情况,为什么不使用老式的java.io.File?

File folder = new File(pathToFolder);
String[] files = folder.list();

测试过,看起来没有列出的文件是打开的。之后,您可以将数组转换为列表或流。除非目录太大或太远,否则我会尝试责怪Path对象和垃圾回收或以某种方式破坏它们。lsof


答案 2

当我没有关闭流时,我遇到了同样的问题(在Windows Server 2012 R2上)。我迭代的所有文件都在读取模式下打开,直到 JVM 关闭。但是,它没有发生在Mac OS X上,并且由于流依赖于和的依赖于操作系统的实现,因此我认为问题也可能与操作系统有关。FileSystemProviderDirectoryStream

与@Ian McLaird的评论相反,在Files.list()文档中提到

如果需要及时处置文件系统资源,则应使用资源试用构造来确保在流操作完成后调用流的 close 方法。

返回的流是一个 ,其 Javadoc 说:DirectoryStream

目录流在创建时打开,并通过调用 close 方法关闭。关闭目录流会释放与该流关联的所有资源。未能关闭流可能会导致资源泄漏。

我的解决方案是遵循建议并使用结构try-with-resources

try (Stream<Path> fileListing = Files.list(directoryPath)) {
    // use the fileListing stream
}

当我正确关闭流(使用上面的构造)时,文件句柄立即被释放。try-with-resources

如果您不关心将文件作为流获取,或者您可以将整个文件列表加载到内存中并自己将其转换为流,则可以使用 IO API:

File directory = new File("/path/to/dir");
File[] files = directory.listFiles();
if (files != null) { // 'files' can be null if 'directory' "does not denote a directory, or if an I/O error occurs."
    // use the 'files' array or convert to a stream:
    Stream<File> fileStream = Arrays.stream(files);
}

我没有遇到任何文件锁定问题。但是,请注意,这两种解决方案都依赖于本机的、依赖于操作系统的代码,因此我建议在您将使用的所有环境中进行测试。


推荐