我正在尝试使用Caffeine作为LRU缓存,因此首先添加的条目将首先被逐出。 跑这段代码:
final Cache<Object, Object> map = Caffeine.newBuilder()
.maximumSize(10)
.initialCapacity(10)
.build();
for (long i=0; i<20;i++) {
map.put(i, i);
}
map.cleanUp();
System.out.println(map.ge.getAllPresent(map.asMap().keySet()));
打印哪些:
{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 19=19}
但我期待
{10=10, 11=11, 12=12, 13=13, 14=14, 15=15, 16=16, 17=17, 18=18, 19=19}
我做错了什么?
答案 0 :(得分:14)
咖啡因不实施LRU作为其缓存驱逐政策。相反,咖啡因使用名为TinyLFU的政策。 Caffeine文档包含Efficiency页面,其中讨论了此设计选择的基本原理。引用该页面:
TinyLfu
依靠频率草图来概率估计条目的历史用法。
由于Caffeine实际上并没有实现LRU,所以当你检查缓存中的条目时,我认为你不能可靠地期望它表现出严格的LRU行为。
如果你绝对必须有LRU行为,那么JDK标准LinkedHashMap
是一个很好的,直截了当的选择。您需要对其进行子类化并使用逻辑覆盖removeEldestEntry
,以便在缓存增长超过您想要的时间时发出信号。如果需要多线程使用,那么您需要使用适当的同步来包装操作。
咖啡因深受Guava Cache的启发,它同样提供并发访问并具有近似的LRU行为。针对Guava缓存快速测试代码显示了类似的结果。我不知道任何标准库可以提供可预测的,外部可观察的LRU结果和真正的并发访问而没有粗粒度锁。
您可能会重新考虑是否真的需要具有严格的,外部可观察的LRU结果。就其本质而言,缓存是快速临时存储,以提供优化的查找。我不希望程序行为根据缓存是否实现严格的LRU,近似的LRU,LFU或其他驱逐策略而发生巨大变化。
这个先前的问题也对Java中的LRU缓存选项进行了很好的讨论。