假设我有一个缓存实现为java.util.Map
,它存储了键的任意值。由于值不是强制性的,缓存会返回java.util.Optional
,并且可以提供java.util.function.Supplier
来计算给定的不存在键的值。
我的第一个天真的方法是
public class Cache0 {
private final Map<String, String> mapping = new HashMap<>();
public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
final Optional<String> valueOptional;
if (this.mapping.containsKey(key)) {
final String value = this.mapping.get(key);
valueOptional = Optional.of(value);
} else {
valueOptional = supplier.get();
if (valueOptional.isPresent()) {
this.mapping.put(key, valueOptional.get());
}
}
return valueOptional;
}
}
但我发现这非常不优雅,当我了解java.util.Map#computeIfAbsent
时,我将代码更改为以下内容
public class Cache1 {
private final Map<String, String> mapping = new HashMap<>();
public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
final String value = this.mapping.computeIfAbsent(key, absentKey -> this.getValue(supplier));
return Optional.ofNullable(value);
}
private String getValue(Supplier<Optional<String>> supplier) {
return supplier.get()
.orElse(null);
}
}
但是现在困扰我的是java.util.Optional#ofNullable
与null
方法的getValue
结果的多余使用,这是为java.util.Map#computeIfAbsent
提供“默认” “价值不能插入地图。
在理想情况下,可能会出现以下情况
public class Cache2 {
private final Map<String, String> mapping = new HashMap<>();
public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
return this.mapping.computeIfAbsent(key, absentKey -> supplier.get());
}
}
如果第二个参数代表空java.util.Map#computeIfAbsent
而java.util.Optional
将返回java.util.Optional#empty
,那么java.util.Optional#empty
会跳过插入,但不幸的是java.util.Map#computeIfAbsent
使用String
作为“默认”插入值不支持java.util.Optional
且代码无法编译。
另一种可能性是存储java.util.Map
到java.util.Optional#empty
的映射,但public class Cache3 {
private final Map<String, Optional<String>> mapping = new HashMap<>();
public Optional<String> get(String key, Supplier<Optional<String>> supplier) {
return this.mapping.computeIfAbsent(key, absentKey -> supplier.get());
}
}
会将Cache1
存储为与我的用例相矛盾的值被迫存储无效映射并在以后手动删除/替换它们。
{{1}}
有人知道更好的方法来处理这种用例,还是我必须回到{{1}}的实现?
答案 0 :(得分:3)
答案 1 :(得分:1)
听起来像你正在重新发明一个Guava LoadingCache(read here about Guava Caches)。虽然这绝对是一项有趣的编程练习,但现有的解决方案经过时间验证,可根据您的需求进行配置,并在极其繁重的负载下工作。
示例定义是:
Cache<Key, Value> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(); // look Ma, no CacheLoader
...
try {
// If the key wasn't in the "easy to compute" group, we need to
// do things the hard way.
cache.get(key, new Callable<Value>() {
@Override
public Value call() throws AnyException {
return doThingsTheHardWay(key);
}
});
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
这有点等同于您的使用场景,即每个键级别的计算可能不同。通常,您不需要这样做,因此您更喜欢缓存中存储的计算方法:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
...
try {
return graphs.get(key);
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}