我需要用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
实现AutoCloseable
和Iterator
,并且放弃使用"for each"样式的for
循环,你可以使用"try with resources"来管理生命周期。但是很麻烦。
另一个可能的解决方案是将文件描述符生命周期的责任从FileWrapper
;例如,将其改为ScannerWrapper
,并使生命周期成为创建/管理该对象的责任。(但是,这从根本上改变了包装器的语义。包装器只能用于生成一次迭代器。)
建议的finalize
方法实际上没有实现任何目标。问题是Scanner
中的FileInputStream
将与Scanner
和FileWrapper.Itr
实例同时成为垃圾。FileInputStream
已经有一个finalize()
方法,它将调用close()
。