Java &引用;“系统中打开的文件太多”;列出递归目录结构时失败

Java &引用;“系统中打开的文件太多”;列出递归目录结构时失败,java,directory-structure,Java,Directory Structure,我(在Java中)实现了一个相当简单的迭代器,以递归目录结构返回文件名,在大约2300个文件之后,“系统中有太多打开的文件”失败(失败实际上是在尝试加载类时,但我认为目录列表是罪魁祸首) 迭代器维护的数据结构是一个堆栈,其中包含在每个级别打开的目录的内容 实际逻辑相当基本: private static class DirectoryIterator implements Iterator<String> { private Stack<File[]>

我(在Java中)实现了一个相当简单的迭代器,以递归目录结构返回文件名,在大约2300个文件之后,“系统中有太多打开的文件”失败(失败实际上是在尝试加载类时,但我认为目录列表是罪魁祸首)

迭代器维护的数据结构是一个堆栈,其中包含在每个级别打开的目录的内容

实际逻辑相当基本:

private static class DirectoryIterator implements Iterator<String> {

        private Stack<File[]> directories;
        private FilenameFilter filter;
        private Stack<Integer> positions = new Stack<Integer>();
        private boolean recurse;
        private String next = null;

        public DirectoryIterator(Stack<File[]> directories, boolean recurse, FilenameFilter filter) {
            this.directories = directories;
            this.recurse = recurse;
            this.filter = filter;
            positions.push(0);
            advance();
        }

        public boolean hasNext() {
            return next != null;
        }

        public String next() {
            String s = next;
            advance();
            return s;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        private void advance() {
            if (directories.isEmpty()) {
                next = null;
            } else {
                File[] files = directories.peek();
                while (positions.peek() >= files.length) {
                    directories.pop();
                    positions.pop();
                    if (directories.isEmpty()) {
                        next = null;
                        return;
                    }
                    files = directories.peek();
                }
                File nextFile = files[positions.peek()];
                if (nextFile.isDirectory()) {
                    int p = positions.pop() + 1;
                    positions.push(p);
                    if (recurse) {
                        directories.push(nextFile.listFiles(filter));
                        positions.push(0);
                        advance();
                    } else {
                        advance();
                    }
                } else {
                    next = nextFile.toURI().toString();
                    count++;
                    if (count % 100 == 0) {
                        System.err.println(count + "  " + next);
                    }
                    int p = positions.pop() + 1;
                    positions.push(p);
                }
            }
        }
    }
私有静态类DirectoryIterator实现迭代器{
私有堆栈目录;
私有文件名过滤器;
私有堆栈位置=新堆栈();
私有布尔递归;
私有字符串next=null;
公共目录迭代器(堆栈目录、布尔递归、文件名筛选器){
this.directories=目录;
this.recurse=recurse;
this.filter=过滤器;
位置。推(0);
前进();
}
公共布尔hasNext(){
返回下一步!=null;
}
公共字符串next(){
字符串s=下一个;
前进();
返回s;
}
公共空间删除(){
抛出新的UnsupportedOperationException();
}
私人预支{
if(directories.isEmpty()){
next=null;
}否则{
File[]files=目录.peek();
while(positions.peek()>=files.length){
目录pop();
positions.pop();
if(directories.isEmpty()){
next=null;
返回;
}
files=目录.peek();
}
File nextFile=files[positions.peek()];
if(nextFile.isDirectory()){
int p=positions.pop()+1;
位置:推(p);
if(递归){
directories.push(nextFile.listFiles(filter));
位置。推(0);
前进();
}否则{
前进();
}
}否则{
next=nextFile.toURI().toString();
计数++;
如果(计数%100==0){
System.err.println(计数+“”+下一步);
}
int p=positions.pop()+1;
位置:推(p);
}
}
}
}
我想了解这需要多少“打开的文件”。这种算法在什么情况下“打开”一个文件,什么时候会再次关闭


我见过一些使用Java 7或Java 8的简洁代码,但我只能使用Java 6。

当调用nextFile.listFiles()时,会打开一个底层文件描述符来读取目录。无法显式关闭此描述符,因此您依赖于垃圾收集。当您的代码从一棵深树上往下看时,它实际上是在收集一堆不能随意收集的nextFile实例

步骤1:在调用advance()之前设置nextFile=null。这将释放用于垃圾收集的对象

步骤2:可能需要在为nextFile置零后调用System.gc(),以鼓励快速垃圾收集。不幸的是,没有办法强制GC

步骤3:您可能需要增加操作系统上的打开文件限制。在Linux上,这可以通过ulimit(1)完成


如果您可以迁移到Java 7或更高版本,那么DirectoryStream将解决您的问题。使用Files.newDirectoryStream(nextFile.toPath())来获取DirectoryStream,而不是使用nextFile.listFiles()。然后可以迭代流,然后关闭()以释放操作系统资源。每个返回的路径都可以用toFile()转换回文件。不过,您可能希望重构为只使用路径而不是文件。

感谢大家的帮助和建议。我确定问题实际上在于迭代器返回文件后如何处理这些文件:“客户机”代码在文件交付时打开文件,并且没有正确整理。返回的文件实际上是并行处理的,这一事实使问题变得复杂

我还重写了DireectoryIterator,如果有人感兴趣,我将与大家分享:

private static class DirectoryIterator implements Iterator<String> {

        private Stack<Iterator<File>> directories;
        private FilenameFilter filter;
        private boolean recurse;
        private String next = null;

        public DirectoryIterator(Stack<Iterator<File>> directories, boolean recurse, FilenameFilter filter) {
            this.directories = directories;
            this.recurse = recurse;
            this.filter = filter;
            advance();
        }

        public boolean hasNext() {
            return next != null;
        }

        public String next() {
            String s = next;
            advance();
            return s;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        private void advance() {
            if (directories.isEmpty()) {
                next = null;
            } else {
                Iterator<File> files = directories.peek();
                while (!files.hasNext()) {
                    directories.pop();
                    if (directories.isEmpty()) {
                        next = null;
                        return;
                    }
                    files = directories.peek();
                }
                File nextFile = files.next();
                if (nextFile.isDirectory()) {
                    if (recurse) {
                        directories.push(Arrays.asList(nextFile.listFiles(filter)).iterator());
                    }
                    advance();
                } else {
                    next = nextFile.toURI().toString();
                }
            }
        }
    }
私有静态类DirectoryIterator实现迭代器{
私有堆栈目录;
私有文件名过滤器;
私有布尔递归;
私有字符串next=null;
公共目录迭代器(堆栈目录、布尔递归、文件名筛选器){
this.directories=目录;
this.recurse=recurse;
this.filter=过滤器;
前进();
}
公共布尔hasNext(){
返回下一步!=null;
}
公共字符串next(){
字符串s=下一个;
前进();
返回s;
}
公共空间删除(){
抛出新的UnsupportedOperationException();
}
私人预支{
if(directories.isEmpty()){
next=null;
}否则{
迭代器文件=目录.peek();
而(!files.hasNext()){
目录pop();
if(directories.isEmpty()){
next=null;
返回;
}
files=目录.peek();
}
File nextFile=files.next();
if(nextFile.isDirectory()){
if(递归){
目录.p