如何为字符串到文件句柄的映射创建清洁器?
how to create cleaner for map of string to file handles?
我有一个class
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; }
};
}
这是文件名到文件句柄的映射。这个想法是让一些文件保持打开状态,以便 Something class 的成员函数可以写入任何打开的文件。
现在我想重写 finalize,以便当对象超出范围时,我们关闭所有文件。但是 finalize 已被弃用。所以想法是调用 cleaner.register(map, runnable) ,其中 runnable 获取 'map' 中的所有 RandomAccessFile 并关闭每个。 IE。类似于 cleaner.register(map, () -> map.values().forEach(close file))
。但这使得 runnable 具有对地图的引用,因此地图永远不会变得无法访问 - class LinkedHashMap.LinkedValues 不是静态的 class.
有什么想法可以做到这一点吗?
您必须将其可达性应控制清理的对象与清理所需的数据分开。
在您的情况下,Something
实例的生命周期将确定文件是否仍在使用中,而地图实例可以在清理操作期间使用,假设您将地图设为私有并且永远不会分发出去。
还有一些重点需要注意。 documentation of Cleaner
表示:
The cleaning action could be a lambda but all too easily will capture the object reference, by referring to fields of the object being cleaned, preventing the object from becoming phantom reachable. Using a static nested class, as above, will avoid accidentally retaining the object reference.
当你使用lambda表达式() -> map.values().forEach(close file))
并且map
是当前Something
实例的字段访问时,你已经隐式捕获了this
。但是,即使你修复了清洁器,让它只引用地图实例,也存在地图本身是 LinkedHashMap
的匿名子 class 和匿名内部 classes 总是有的问题对其外部实例的隐式引用。
您可以通过在静态上下文中执行初始化来解决这两个问题:
class Something {
private Map<String, RandomAccessFile> map = initializeMapAndCleaner(this);
static final Cleaner CLEANER = Cleaner.create();
static Map<String, RandomAccessFile> initializeMapAndCleaner(Object instance) {
LinkedHashMap<String, RandomAccessFile> map = new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(Map.Entry<String, RandomAccessFile> eldest) {
/*also closes the file if it returns true;*/
}
@Override
public RandomAccessFile remove(Object filename) { /*closes the file too;*/ }
};
CLEANER.register(instance, () -> map.values().forEach(f -> {/*close file*/}));
return map;
}
}
通过使用 static
方法,不能有任何对 this
的隐式引用,并且 instance
参数中提供的显式引用被故意键入为 Object
,以防止意外访问映射 class 或清理操作中的成员。
但请注意,LinkedHashMap
不保证所有删除操作都通过那个被覆盖的 remove
方法。此外,替换操作不使用 remove
。当 remove
是 Something
class 使用的唯一操作(除 put
之外)时,它可能适用于您的内部使用,但在这种情况下,它也是可能的并且使文件关闭调用者职责的清洁器。
就是说,您根本不应该实施清理器。如果所有的清理工作都是为了关闭 RandomAccessFile
,它已经过时了,因为当 RandomAccessFile
变得无法访问而不被关闭时,已经有一个清理器关闭了底层资源。如果依赖那个清洁工感觉不对劲,那你就走对了。但是依靠 Something
的清洁器并没有更好的办法。
一般情况下,变量作用域和对象可达性只是远程连接。请参阅 Can java finalize an object when it is still in scope? or finalize() called on strongly reachable objects in Java 8 for a connected real life problem. But of course, the timing issues described in When is the finalize() method called in Java? 也适用于清洁工。垃圾收集器可能永远不会 运行,例如当有足够的内存时,或者它可能 运行 但不会收集所有无法访问的对象,例如当它回收足够的内存供应用程序继续运行时。
当你想在变量范围的末尾进行清理时,try-with-resources Statement 是可行的方法。您只需实现 AutoCloseable
或其子类型,提供一个 close()
方法来关闭所有文件。
class Something implements Closeable { // java.io.Closeable is suitable here
…
@Override
public void close() throws IOException {
for(RandomAccessFile f: map.values()) f.close();
map.clear();
}
}
那你就可以轻松地说
try(Something sth = new Something(…)) {
// use sth
}
// safely closed
我有一个class
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; }
};
}
这是文件名到文件句柄的映射。这个想法是让一些文件保持打开状态,以便 Something class 的成员函数可以写入任何打开的文件。
现在我想重写 finalize,以便当对象超出范围时,我们关闭所有文件。但是 finalize 已被弃用。所以想法是调用 cleaner.register(map, runnable) ,其中 runnable 获取 'map' 中的所有 RandomAccessFile 并关闭每个。 IE。类似于 cleaner.register(map, () -> map.values().forEach(close file))
。但这使得 runnable 具有对地图的引用,因此地图永远不会变得无法访问 - class LinkedHashMap.LinkedValues 不是静态的 class.
有什么想法可以做到这一点吗?
您必须将其可达性应控制清理的对象与清理所需的数据分开。
在您的情况下,Something
实例的生命周期将确定文件是否仍在使用中,而地图实例可以在清理操作期间使用,假设您将地图设为私有并且永远不会分发出去。
还有一些重点需要注意。 documentation of Cleaner
表示:
The cleaning action could be a lambda but all too easily will capture the object reference, by referring to fields of the object being cleaned, preventing the object from becoming phantom reachable. Using a static nested class, as above, will avoid accidentally retaining the object reference.
当你使用lambda表达式() -> map.values().forEach(close file))
并且map
是当前Something
实例的字段访问时,你已经隐式捕获了this
。但是,即使你修复了清洁器,让它只引用地图实例,也存在地图本身是 LinkedHashMap
的匿名子 class 和匿名内部 classes 总是有的问题对其外部实例的隐式引用。
您可以通过在静态上下文中执行初始化来解决这两个问题:
class Something {
private Map<String, RandomAccessFile> map = initializeMapAndCleaner(this);
static final Cleaner CLEANER = Cleaner.create();
static Map<String, RandomAccessFile> initializeMapAndCleaner(Object instance) {
LinkedHashMap<String, RandomAccessFile> map = new LinkedHashMap<>() {
@Override
protected boolean removeEldestEntry(Map.Entry<String, RandomAccessFile> eldest) {
/*also closes the file if it returns true;*/
}
@Override
public RandomAccessFile remove(Object filename) { /*closes the file too;*/ }
};
CLEANER.register(instance, () -> map.values().forEach(f -> {/*close file*/}));
return map;
}
}
通过使用 static
方法,不能有任何对 this
的隐式引用,并且 instance
参数中提供的显式引用被故意键入为 Object
,以防止意外访问映射 class 或清理操作中的成员。
但请注意,LinkedHashMap
不保证所有删除操作都通过那个被覆盖的 remove
方法。此外,替换操作不使用 remove
。当 remove
是 Something
class 使用的唯一操作(除 put
之外)时,它可能适用于您的内部使用,但在这种情况下,它也是可能的并且使文件关闭调用者职责的清洁器。
就是说,您根本不应该实施清理器。如果所有的清理工作都是为了关闭 RandomAccessFile
,它已经过时了,因为当 RandomAccessFile
变得无法访问而不被关闭时,已经有一个清理器关闭了底层资源。如果依赖那个清洁工感觉不对劲,那你就走对了。但是依靠 Something
的清洁器并没有更好的办法。
一般情况下,变量作用域和对象可达性只是远程连接。请参阅 Can java finalize an object when it is still in scope? or finalize() called on strongly reachable objects in Java 8 for a connected real life problem. But of course, the timing issues described in When is the finalize() method called in Java? 也适用于清洁工。垃圾收集器可能永远不会 运行,例如当有足够的内存时,或者它可能 运行 但不会收集所有无法访问的对象,例如当它回收足够的内存供应用程序继续运行时。
当你想在变量范围的末尾进行清理时,try-with-resources Statement 是可行的方法。您只需实现 AutoCloseable
或其子类型,提供一个 close()
方法来关闭所有文件。
class Something implements Closeable { // java.io.Closeable is suitable here
…
@Override
public void close() throws IOException {
for(RandomAccessFile f: map.values()) f.close();
map.clear();
}
}
那你就可以轻松地说
try(Something sth = new Something(…)) {
// use sth
}
// safely closed