Java中易于使用的LRU缓存

77

我知道这很容易实现,但我想重用已经存在的东西。

我想解决的问题是,我会为不同的页面、角色等加载配置(从XML中),因此输入的组合可能会变得非常多(但在99%的情况下不会)。为了处理这1%,我想在缓存中设置一些最大数量的项目...

到目前为止,我已经找到了apache commons中的org.apache.commons.collections.map.LRUMap,并且看起来很好,但还想检查其他的东西。有什么推荐吗?


@Suporwski,你是怎么解决这个问题的? - Hunt
@Hunt 我有来自commons的LRUMap。 - Juraj
这里还有一个非常类似的问题 链接 - Mifeet
5个回答

125
您可以使用 LinkedHashMap(Java 1.4+):
// Create cache
final int MAX_ENTRIES = 100;
Map cache = new LinkedHashMap(MAX_ENTRIES+1, .75F, true) {
    // This method is called just after a new entry has been added
    public boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }
};

// Add to cache
Object key = "key";
cache.put(key, object);

// Get object
Object o = cache.get(key);
if (o == null && !cache.containsKey(key)) {
    // Object not in cache. If null is not a possible value in the cache,
    // the call to cache.contains(key) is not needed
}

// If the cache is to be used by multiple threads,
// the cache must be wrapped with code to synchronize the methods
cache = (Map)Collections.synchronizedMap(cache);

1
好的,我决定使用LRUMap。 - Juraj
9
我今天发现了LinkedHashMap的这个方面,它非常简单而惊人。教训是:要熟悉你所使用的API。 - Julien Grenier
1
以下是一个基于java.util.LinkedHashMap的LRU缓存类的清晰示例,同时提供了一个测试:链接 - Champ
负载因子真的应该是0.75吗?就像您从未希望Map包含超过MAX_ENTRIES条目一样。 - Tim P
LinkedHashMap 移除最老的条目,而 LRUMap 移除最近最少使用的条目。两者服务于不同的用例。 - Vikash
显示剩余3条评论

35

这是一个老问题,但为了后人,我想列出ConcurrentLinkedHashMap,它是线程安全的,不像LRUMap。使用非常简单:

ConcurrentMap<K, V> cache = new ConcurrentLinkedHashMap.Builder<K, V>()
    .maximumWeightedCapacity(1000)
    .build();

文档中提供了一些很好的示例,例如如何使LRU缓存基于大小而不是项数。


7
Caffeine是Java 8重写的缓存库,速度更快,并提供了更多特性。你可以在这里查看Caffeine的基准测试结果,以及Caffeine缓存的更多特性 - Ben Manes

15

这是我的实现方式,可以让我在内存中保留最佳数量的元素。

重点是我不需要跟踪当前正在使用的对象,因为我同时使用LinkedHashMap和WeakHashMap组合来处理MRU(最近最少使用)对象和LRU(最久未使用)对象。 因此,缓存容量不会小于MRU大小加上GC允许我保留的内容。每当对象从MRU中移除时,它们会在LRU中停留,只要GC允许。

public class Cache<K,V> {
final Map<K,V> MRUdata;
final Map<K,V> LRUdata;

public Cache(final int capacity)
{
    LRUdata = new WeakHashMap<K, V>();

    MRUdata = new LinkedHashMap<K, V>(capacity+1, 1.0f, true) {
        protected boolean removeEldestEntry(Map.Entry<K,V> entry)
        {
            if (this.size() > capacity) {
                LRUdata.put(entry.getKey(), entry.getValue());
                return true;
            }
            return false;
        };
    };
}

public synchronized V tryGet(K key)
{
    V value = MRUdata.get(key);
    if (value!=null)
        return value;
    value = LRUdata.get(key);
    if (value!=null) {
        LRUdata.remove(key);
        MRUdata.put(key, value);
    }
    return value;
}

public synchronized void set(K key, V value)
{
    LRUdata.remove(key);
    MRUdata.put(key, value);
}
}

这是一个巧妙的方法。因此,LRU缓存只存储已从大小受限的MRU缓存中过期的内容。就像软件第二级缓存一样。不错! - Ogre Psalm33
如果 LRUdata 中存在数据但 MRUdata 中缺失,则可以向应用程序日志发出有用的警告/提示,以增加缓存大小。 - Mike
它将最老的项目移动到WeakHashMap而不是实际的LRU项目。 - Vikash

1

我也曾遇到过同样的问题,但找不到好的库...所以我自己创建了一个。

simplelrucache提供线程安全、非常简单、非分布式LRU缓存与TTL支持。它提供了两种实现方式:

  • 基于ConcurrentLinkedHashMap的并发实现
  • 基于LinkedHashMap的同步实现

您可以在这里找到它。


你能否发布你的库到Maven Central吗? - Jakub Jirutka
1
版本1.0现在应该已经在中央仓库了 :) - Daimon

1

这里是一个非常简单易用的Java LRU缓存。 尽管它短小简单,但是它具备生产级别的质量。 代码已经解释(请查看README.md)并且包含一些单元测试。


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