13

我正在尝试使用咖啡因作为 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}

我究竟做错了什么?

4

1 回答 1

17

Caffeine 没有实现 LRU 作为其缓存驱逐策略。相反,Caffeine 使用名为TinyLFU的策略。Caffeine 文档包括一个关于Efficiency的页面,其中讨论了这种设计选择的基本原理。引用该页面:

TinyLfu依靠频率草图来概率估计条目的历史使用情况。

由于 Caffeine 实际上并没有实现 LRU,我认为当您检查缓存中的条目时,我认为您不能可靠地期望它表现出严格的 LRU 行为。

如果您绝对必须具有 LRU 行为,那么 JDK 标准LinkedHashMap是一个很好、直接的选择。您需要对其进行子类化并removeEldestEntry使用逻辑覆盖以在缓存变得比您想要的大时发出信号。如果需要使用多线程,那么您需要使用适当的同步来包装操作。

Caffeine 深受Guava Cache的启发,它同样提供并发访问并具有近似的 LRU 行为。针对 Guava 缓存对您的代码进行的快速测试显示了类似的结果。我不知道有任何标准库可以在没有粗粒度锁的情况下提供可预测的、外部可观察的 LRU 结果和真正的并发访问。

您可能会重新考虑是否真的需要严格的、外部可观察的 LRU 结果。就其本质而言,缓存是一种快速的临时存储,可提供优化的查找。我不希望程序行为会根据缓存是否实现严格的 LRU、LRU 的近似值、LFU 或其他一些驱逐策略而发生巨大变化。

这个先前的问题也对Java中的LRU缓存选项进行了很好的讨论。

你将如何在 Java 中实现 LRU 缓存?

于 2016-01-09T17:26:56.323 回答