LinkedHashMap LRU缓存 - 确定哪些值将被删除?

4

背景信息

您可以使用LinkedHashMap来创建一个LRU缓存,如此链接所示。基本上,您需要:

  • 扩展链式哈希映射。
  • 提供容量参数。
  • 使用参数初始化超类(LinkedHashMap),告诉它容量、比例因子(永远不应该使用)和保持插入/引用顺序的项目。
  • 重写removeEldestEntry以在容量被超出时删除最旧的条目。

我的问题

这是一个相当标准的LRU缓存实现。但是我无法弄清楚如何在LinkedHashMap由于最近没有使用而删除一个条目时得到通知。

我知道我可以让removeEldestEntry提供某种形式的通知...但是否有任何方法在向底层映射插入新元素(put)时立即检索从缓存中删除的元素?或者,是否有一种方法查询最后一个从缓存中删除的项目?

2个回答

2
你可以通过巧妙地使用线程本地存储来使其工作:
class LRUCacheLHM<K,V> extends LinkedHashMap<K,V> {

    private int capacity;

    public LRUCacheLHM(int capacity) {
        //1 extra element as add happens before remove (101), and load factor big
        //enough to avoid triggering resize.  True = keep in access order.
        super(capacity + 1, 1.1f, true);
        this.capacity = capacity;
    }
    private ThreadLocal<Map.Entry<K,V>> removed = new ThreadLocal<Map.Entry<K,V>>();
    private ThreadLocal<Boolean> report = new ThreadLocal<Boolean>();
    {
        report.set(false);
    }
    @Override
    public boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        boolean res = size() > capacity;
        if (res && report.get()) {
            removed.set(eldest);
        }
        return res;
    }
    public Map.Entry<K,V> place(K k, V v) {
        report.set(true);
        put(k, v);
        try {
            return removed.get();
        } finally {
            removed.set(null);
            report.set(false);
        }
    }

}

示例。

place(K,V) 方法背后的思想是通过将线程本地的 report 标志设置为 true 来向 removeEldestEntry 发出信号,表示我们希望获取最老的条目。当 removeEldestEntry 看到这个标志并知道正在删除一个条目时,它会将最老的条目放在同样是线程本地的 report 变量中。

调用 put 方法时会调用 removeEldestEntry。然后最老的条目要么是 null,要么就坐在 report 变量中准备被收集。

removed 上调用 set(null) 是很重要的,以避免悬空内存泄漏。


所以...你正在使用place()代替put来创建一个能够返回已删除值的put()版本,这很酷。但是我对线程本地变量有点困惑。相比直接设置本地成员,它们有什么好处呢?我认为底层的linkedhashmap数据结构不是同步的,因此可能无法被多个线程使用。我知道我可能忽略了什么 :) - John Humphreys
抱歉,我在发布后编辑了那条评论好几遍,哈哈。 - John Humphreys
@JohnHumphreys-w00te 是的,这就是想法。使用 put 是不合适的,因为它会破坏 Map 的契约。线程本地变量存在的目的是允许并发(我将它们设置为非静态以更好地使用通用类型,但静态也可以)。如果没有线程本地变量,您最终会将值存储在实例变量中,在缓存并发使用期间会引入竞争条件,即使您在其周围放置了同步包装器。 - Sergey Kalinichenko

1
有没有办法在新元素插入(put)到底层映射中时,从缓存中检索被删除的元素?removeEldestEntry会被通知要删除的条目。如果您想使其动态可配置,可以添加此方法调用的侦听器。从Javadoc中得知,参数eldest是地图中最近插入的条目,如果这是一个访问排序的地图,则为最近访问的条目。如果此方法返回true,则将删除该条目。如果在进行此调用的put或putAll调用之前地图为空,则这将是刚刚插入的条目;换句话说,如果地图包含单个条目,则最老的条目也是最新的。

.

有没有一种方法可以查询从缓存中删除的最后一个项目?

最后一个被删除的项目已经被删除了,但是您可以让子类将此条目存储在字段中,以便稍后检索。


1
你关于让子类存储它的最后一点正是我所需要的。出于某种原因,我没有想得那么深入:) 谢谢! - John Humphreys

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接