Java 如何为字符串到文件句柄的映射创建清理器?

Java 如何为字符串到文件句柄的映射创建清理器?,java,garbage-collection,java-9,resource-cleanup,Java,Garbage Collection,Java 9,Resource Cleanup,我有一节课 class Something { private Map<String, RandomAccessFile> map = new LinkedHashMap<>() { @Override protected boolean removeEldestEntry(...) { also closes the file if it returns true; } @Override pub

我有一节课

class Something {
    private Map<String, RandomAccessFile> map = new LinkedHashMap<>() {
        @Override
        protected boolean removeEldestEntry(...) { also closes the file if it returns true; }

        @Override
        public RandomAccessFile remove(Object filename) { closes the file too; }
    };
}
分类{
私有映射映射=新建LinkedHashMap(){
@凌驾
受保护的布尔removeEldestEntry(…){如果返回true,也会关闭该文件;}
@凌驾
public RandomAccessFile删除(对象文件名){也关闭文件;}
};
}
这是文件名到文件句柄的映射。其思想是保持几个文件处于打开状态,以便Something类的成员函数可以写入任何打开的文件

现在我想覆盖finalize,这样当对象超出范围时,我们就关闭所有文件。但finalize已被弃用。因此,我们的想法是调用cleaner.register(map,runnable),其中runnable获取“map”中的所有RandomAccessFile并关闭每个文件。例如,类似于
cleaner.register(映射,()->map.values().forEach(关闭文件))
。但是这使得runnable具有对映射的引用,因此映射永远不会变得不可访问-类LinkedHashMap.LinkedValues不是静态类


有什么办法可以做到这一点吗?

您必须将可访问性控制清理的对象与清理所需的数据分开

在您的情况下,
Something
实例的生存期将决定文件是否仍在使用中,而map实例可以在清理操作期间使用,前提是您保持映射的私有性并且从不分发它

还有一些要点需要注意。报告说:

清理操作可以是lambda,但很容易通过引用正在清理的对象的字段来捕获对象引用,从而防止对象变得无法访问。如上所述,使用静态嵌套类将避免意外地保留对象引用

当您使用lambda表达式
()->map.values().forEach(关闭文件))
并且
map
是当前
Something
实例的字段访问时,您已经隐式地捕获了
。但是,即使修复了cleaner,让它只引用map实例,也存在一个问题,即map本身是
LinkedHashMap
的匿名子类,并且匿名内部类总是隐式引用它们的外部实例

您可以通过在静态上下文中执行初始化来修复这两个问题:

分类{
私有映射映射=InitializeMapCleaner(此);
静态最终清洁器=Cleaner.create();
静态映射初始值EmaPandCleaner(对象实例){
LinkedHashMap=新建LinkedHashMap(){
@凌驾
受保护的布尔重构(Map.Entry最早){
/*如果返回true,也会关闭文件*/
}
@凌驾
公共随机访问文件删除(对象文件名){/*也关闭文件;*/}
};
register(实例,()->map.values().forEach(f->{/*close file*/}));
返回图;
}
}
通过使用
静态
方法,对
不能有任何隐式引用,并且
实例
参数中提供的显式引用被故意键入为
对象
,以使意外访问map类中的成员或清除操作更加困难

但是请注意,
LinkedHashMap
并不能保证所有删除操作都通过单个重写的
remove
方法。此外,更换操作不使用
删除
。当
Something
类使用的
remove
put
之外的唯一操作时,它可能适用于您的内部使用,但在这种情况下,使文件关闭成为调用者的职责也是可能的和更干净的


也就是说,你根本不应该使用清洁剂。如果所有的清理都是关闭
RandomAccessFile
,那么它就过时了,因为当
RandomAccessFile
不关闭就无法访问时,已经有一个清理程序关闭基础资源。如果依赖那个清洁工感觉不舒服,那你就走对了。但是,依赖于某种清洁剂绝不是更好的选择

通常,变量作用域和对象可达性仅远程连接。查看或以了解与现实生活相关的问题。当然,中描述的时间问题也适用于清洁器。垃圾收集器可能永远不会运行,例如当内存足够时,或者它可能运行但不收集所有无法访问的对象,例如当它回收了足够的内存以便应用程序继续运行时

当您想在变量作用域的末尾进行清理时,最好的方法是。您只需实现
AutoCloseable
或其子类型,提供一个
close()
方法来关闭所有文件

class某物实现了Closeable{//java.io.Closeable适合这里
…
@凌驾
public void close()引发IOException{
对于(随机访问文件f:map.values())f.close();
map.clear();
}
}
那么你可以很容易地说

试试(某物某物=新的某物(…){ //使用某物 } //安全关闭
如果注册表的签名是
公共可清洗注册表(Object obj,Consumer action)
就好了,这里不需要使用Cleaner。但是,在您的示例中,
CLEANER.register(实例,()->map.values().forEach(…)
cleanable有一个对map.values()的引用,这是一个特殊的非嵌套类,包含对map的引用,所以它不能正常工作?然而,正如您所指出的,我将代码更改为不使用cleaner,并将很快使其实现自动关闭@霍尔格。@snaran它直接引用了
map
values()
方法仅在清洗器运行时调用。但是,如前所述,重要的一点是