在类实例被垃圾收集之前调用Scanner#close()



我需要用Iterator编写一个类,它遍历文件并在每次调用Iterator#next()时返回文件的一行。问题是,我不知道什么时候Iterator实例的引用将变得不可访问(也许garbage-collected一段时间之后),所以我不能调用Scanner#close()

所以问题是,如果有任何方法调用Scanner#close()之后,迭代器实例的引用变得不可达,但之前它被垃圾收集?

public class FileWrapper implements Iterable<String> {
    private File file;
    @Override
    public Iterator<String> iterator() {
            return new Itr();
    }
    private class Itr implements Iterator<String> {
        private Scanner scanner;
        public Itr() {
            scanner = new Scanner(file);
        }
        @Override
        public boolean hasNext() {
            return scanner.hasNextLine();
        }
        @Override
        public String next() {
            return scanner.nextLine();
        }
    }
}

如果可以的话,我建议使用Closeable方法,因为它是确定性的。

你可以覆盖finalize,但这是非常有问题的。不能保证finalize会被调用。终结器还会对垃圾收集器施加惩罚。如果你能找到避免的方法,就不要搅乱最后的过程。

还要注意,这个清理将固定在内存中。您可能会在耗尽内存之前很久就耗尽文件句柄。这使得非内存资源的确定性清理成为一个更好的选择。

还有第三种方法,即使用虚引用。

public class FileWrapper implements Iterable<String> {
    private File file;
    // Keep track of phantom references to iterators
    private static ReferenceQueue<Itr> references = new ReferenceQueue<>();
    static {
        new Thread(new Runnable() {
            public void run() {
                while(true) {
                    // Block until an iterator is about to be annihilated
                    Reference<Itr> ref = references.remove();
                    Itr aboutToDie = ref.get();
                    try {
                        aboutToDie.scanner.close();
                    }
                    catch(IOException ex) {
                        // Already closed?
                    }
                }
            }
        }).start();
    }
    @Override
    public Iterator<String> iterator() {
        return new Itr();
    }
    private class Itr implements Iterator<String> {
        private Scanner scanner;
        public Itr() throws FileNotFoundException {
            scanner = new Scanner(file);
            synchronized(references) {
                new PhantomReference(scanner, references);
            }
        }
        @Override
        public boolean hasNext() {
            return scanner.hasNextLine();
        }
        @Override
        public String next() {
            return scanner.nextLine();
        }
    }
}

幻影引用有点酷。与强引用或弱引用不同(强引用或弱引用会影响被收集的引用的可达性和可用性),虚引用对引用完全没有控制。当所有的强引用或弱引用都消失了,并且referent被最终确定时,在最终湮灭之前剩下的就是幻影引用。

此时,虚引用将被添加到引用队列中,您可以在引用队列中获取它以执行预剖析处理。

注意:Google collection有FinalizablePhantomReference为你管理后台线程

理想的解决方案是使Iterator也实现Closeable,并使iterator()的调用者负责调用close()。可闭迭代器的close()方法将关闭Scanner

不幸的是,把它塞进一个通过Iterable/Iterator api管理迭代器生命周期的上下文中.....会有问题。当然,你不可能得到

 for (String s : someFileWrapper) {
      ....
 }

在循环末尾关闭Iterator

如果你让Itr实现AutoCloseableIterator,并且放弃使用"for each"样式的for循环,你可以使用"try with resources"来管理生命周期。但是很麻烦。


另一个可能的解决方案是将文件描述符生命周期的责任从FileWrapper;例如,将其改为ScannerWrapper,并使生命周期成为创建/管理该对象的责任。(但是,这从根本上改变了包装器的语义。包装器只能用于生成一次迭代器。)


建议的finalize方法实际上没有实现任何目标。问题是Scanner中的FileInputStream将与ScannerFileWrapper.Itr实例同时成为垃圾。FileInputStream已经有一个finalize()方法,它将调用close()

最新更新