>假设我有一个实现为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
的冗余使用与getValue
方法的null
结果相结合,该方法需要为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.Optional
并返回java.util.Optional#empty
,则java.util.Map#computeIfAbsent
将跳过插入,但不幸的是,不支持使用 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
实现?
要做这种事情,我通常在地图中使用 Optional - 这样 map.get()!=null
表示我已经缓存了访问权限,map.get().isPresent()
告诉我是否返回了合理的值。
在这种情况下,我会使用一个Suplier<String>
,当值不存在时返回null
。然后实现将如下所示:
public class Cache {
private final Map<String, Optional<String>> mapping = new HashMap<>();
public Optional<String> get(String key, Suplier<String> supplier) {
return mapping.computeIfAbsent(key,
unused -> Optional.ofNullable(supplier.get()) );
}
}
缺少的键确实会插入到地图中,但标记为缺失。
在我看来,您正在重新发明番石榴加载缓存(阅读此处有关番石榴缓存的信息(。虽然这绝对是一个有趣的编程练习,但现有的解决方案是经过时间验证的,可以根据您的需求进行配置,并在极重的负载下工作。
一个示例定义是:
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());
}