你将如何在 Java 中实现 LRU 缓存?
Posted
技术标签:
【中文标题】你将如何在 Java 中实现 LRU 缓存?【英文标题】:How would you implement an LRU cache in Java? 【发布时间】:2010-09-18 07:16:25 【问题描述】:请不要说 EHCache 或 OSCache 等。出于这个问题的目的,假设我想只使用 SDK 来实现我自己的(边做边学)。鉴于缓存将在多线程环境中使用,您将使用哪些数据结构?我已经使用LinkedHashMap 和Collections#synchronizedMap 实现了一个,但我很好奇是否有任何新的并发集合会是更好的候选者。
更新:当我发现这个金块时,我正在阅读Yegge's latest:
如果您需要恒定时间的访问并希望维护插入顺序,那么最好的方法就是 LinkedHashMap,这是一种真正美妙的数据结构。唯一可能更精彩的方法是如果有一个并发版本。但是很可惜。
在使用上面提到的LinkedHashMap
+ Collections#synchronizedMap
实现之前,我的想法几乎完全相同。很高兴知道我没有忽略一些事情。
根据目前的答案,对于高并发 LRU,我最好的选择是使用 LinkedHashMap
使用的一些相同逻辑来扩展 ConcurrentHashMap。
【问题讨论】:
O(1)
所需版本:***.com/questions/23772102/…
非常相似的问题也here
LRU 实现示例:here
【参考方案1】:
我喜欢很多这些建议,但现在我想我会坚持使用LinkedHashMap
+ Collections.synchronizedMap
。如果将来我真的重温这一点,我可能会以与 LinkedHashMap
扩展 HashMap
相同的方式扩展 ConcurrentHashMap
。
更新:
根据要求,这是我当前实现的要点。
private class LruCache<A, B> extends LinkedHashMap<A, B>
private final int maxEntries;
public LruCache(final int maxEntries)
super(maxEntries + 1, 1.0f, true);
this.maxEntries = maxEntries;
/**
* Returns <tt>true</tt> if this <code>LruCache</code> has more entries than the maximum specified when it was
* created.
*
* <p>
* This method <em>does not</em> modify the underlying <code>Map</code>; it relies on the implementation of
* <code>LinkedHashMap</code> to do that, but that behavior is documented in the JavaDoc for
* <code>LinkedHashMap</code>.
* </p>
*
* @param eldest
* the <code>Entry</code> in question; this implementation doesn't care what it is, since the
* implementation is only dependent on the size of the cache
* @return <tt>true</tt> if the oldest
* @see java.util.LinkedHashMap#removeEldestEntry(Map.Entry)
*/
@Override
protected boolean removeEldestEntry(final Map.Entry<A, B> eldest)
return super.size() > maxEntries;
Map<String, String> example = Collections.synchronizedMap(new LruCache<String, String>(CACHE_SIZE));
【讨论】:
我想在这里使用封装而不是继承。这是我从 Effective Java 中学到的。 @KapilD 已经有一段时间了,但我几乎肯定LinkedHashMap
的 JavaDocs 明确支持这种创建 LRU 实现的方法。
@HankGay Java 的 LinkedHashMap(第三个参数 = true)不是 LRU 缓存。这是因为重新放置条目不会影响条目的顺序(真正的 LRU 缓存会将最后插入的条目放在迭代顺序的后面,而不管该条目最初是否存在于缓存中)
@Pacerier 我根本看不到这种行为。使用启用了 accessOrder 的映射,所有操作都会创建一个最近使用(最新)的条目:初始插入、值更新和值检索。我错过了什么吗?
@Pacerier “重新输入条目不会影响条目的顺序”,这是不正确的。如果您查看 LinkedHashMap 的实现,对于“put”方法,它从 HashMap 继承实现。 HashMap 的 Javadoc 说“如果映射先前包含键的映射,则替换旧值”。而如果你查看它的源码,在替换旧值的时候,它会调用recordAccess方法,而在LinkedHashMap的recordAccess方法中是这样的: if (lm.accessOrder) lm.modCount++;消除(); addBefore(lm.header);【参考方案2】:
如果我今天从头开始,我会使用 Guava 的 CacheBuilder
。
【讨论】:
【参考方案3】:这是第二轮。
第一轮是我想出的,然后我重新阅读了 cmets,其中的域在我的脑海中更加根深蒂固。
所以这里是最简单的版本,带有一个单元测试,表明它可以基于其他一些版本工作。
先是非并发版本:
import java.util.LinkedHashMap;
import java.util.Map;
public class LruSimpleCache<K, V> implements LruCache <K, V>
Map<K, V> map = new LinkedHashMap ( );
public LruSimpleCache (final int limit)
map = new LinkedHashMap <K, V> (16, 0.75f, true)
@Override
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest)
return super.size() > limit;
;
@Override
public void put ( K key, V value )
map.put ( key, value );
@Override
public V get ( K key )
return map.get(key);
//For testing only
@Override
public V getSilent ( K key )
V value = map.get ( key );
if (value!=null)
map.remove ( key );
map.put(key, value);
return value;
@Override
public void remove ( K key )
map.remove ( key );
@Override
public int size ()
return map.size ();
public String toString()
return map.toString ();
true 标志将跟踪gets 和puts 的访问。请参阅 JavaDocs。构造函数没有 true 标志的 removeEdelstEntry 只会实现 FIFO 缓存(请参阅下面有关 FIFO 和 removeEldestEntry 的注释)。
这是证明它可以用作 LRU 缓存的测试:
public class LruSimpleTest
@Test
public void test ()
LruCache <Integer, Integer> cache = new LruSimpleCache<> ( 4 );
cache.put ( 0, 0 );
cache.put ( 1, 1 );
cache.put ( 2, 2 );
cache.put ( 3, 3 );
boolean ok = cache.size () == 4 || die ( "size" + cache.size () );
cache.put ( 4, 4 );
cache.put ( 5, 5 );
ok |= cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 2 ) == 2 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
ok |= cache.getSilent ( 4 ) == 4 || die ();
ok |= cache.getSilent ( 5 ) == 5 || die ();
cache.get ( 2 );
cache.get ( 3 );
cache.put ( 6, 6 );
cache.put ( 7, 7 );
ok |= cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 2 ) == 2 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
ok |= cache.getSilent ( 4 ) == null || die ();
ok |= cache.getSilent ( 5 ) == null || die ();
if ( !ok ) die ();
现在是并发版本...
包 org.boon.cache;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LruSimpleConcurrentCache<K, V> implements LruCache<K, V>
final CacheMap<K, V>[] cacheRegions;
private static class CacheMap<K, V> extends LinkedHashMap<K, V>
private final ReadWriteLock readWriteLock;
private final int limit;
CacheMap ( final int limit, boolean fair )
super ( 16, 0.75f, true );
this.limit = limit;
readWriteLock = new ReentrantReadWriteLock ( fair );
protected boolean removeEldestEntry ( final Map.Entry<K, V> eldest )
return super.size () > limit;
@Override
public V put ( K key, V value )
readWriteLock.writeLock ().lock ();
V old;
try
old = super.put ( key, value );
finally
readWriteLock.writeLock ().unlock ();
return old;
@Override
public V get ( Object key )
readWriteLock.writeLock ().lock ();
V value;
try
value = super.get ( key );
finally
readWriteLock.writeLock ().unlock ();
return value;
@Override
public V remove ( Object key )
readWriteLock.writeLock ().lock ();
V value;
try
value = super.remove ( key );
finally
readWriteLock.writeLock ().unlock ();
return value;
public V getSilent ( K key )
readWriteLock.writeLock ().lock ();
V value;
try
value = this.get ( key );
if ( value != null )
this.remove ( key );
this.put ( key, value );
finally
readWriteLock.writeLock ().unlock ();
return value;
public int size ()
readWriteLock.readLock ().lock ();
int size = -1;
try
size = super.size ();
finally
readWriteLock.readLock ().unlock ();
return size;
public String toString ()
readWriteLock.readLock ().lock ();
String str;
try
str = super.toString ();
finally
readWriteLock.readLock ().unlock ();
return str;
public LruSimpleConcurrentCache ( final int limit, boolean fair )
int cores = Runtime.getRuntime ().availableProcessors ();
int stripeSize = cores < 2 ? 4 : cores * 2;
cacheRegions = new CacheMap[ stripeSize ];
for ( int index = 0; index < cacheRegions.length; index++ )
cacheRegions[ index ] = new CacheMap<> ( limit / cacheRegions.length, fair );
public LruSimpleConcurrentCache ( final int concurrency, final int limit, boolean fair )
cacheRegions = new CacheMap[ concurrency ];
for ( int index = 0; index < cacheRegions.length; index++ )
cacheRegions[ index ] = new CacheMap<> ( limit / cacheRegions.length, fair );
private int stripeIndex ( K key )
int hashCode = key.hashCode () * 31;
return hashCode % ( cacheRegions.length );
private CacheMap<K, V> map ( K key )
return cacheRegions[ stripeIndex ( key ) ];
@Override
public void put ( K key, V value )
map ( key ).put ( key, value );
@Override
public V get ( K key )
return map ( key ).get ( key );
//For testing only
@Override
public V getSilent ( K key )
return map ( key ).getSilent ( key );
@Override
public void remove ( K key )
map ( key ).remove ( key );
@Override
public int size ()
int size = 0;
for ( CacheMap<K, V> cache : cacheRegions )
size += cache.size ();
return size;
public String toString ()
StringBuilder builder = new StringBuilder ();
for ( CacheMap<K, V> cache : cacheRegions )
builder.append ( cache.toString () ).append ( '\n' );
return builder.toString ();
你可以看到我为什么先介绍非并发版本。以上尝试创建一些条带以减少锁争用。所以我们对键进行散列,然后查找该散列以找到实际的缓存。这使得限制大小更像是一个建议/粗略猜测,取决于您的密钥哈希算法的传播程度。
这是显示并发版本可能有效的测试。 :) (在火下测试才是真正的方法)。
public class SimpleConcurrentLRUCache
@Test
public void test ()
LruCache <Integer, Integer> cache = new LruSimpleConcurrentCache<> ( 1, 4, false );
cache.put ( 0, 0 );
cache.put ( 1, 1 );
cache.put ( 2, 2 );
cache.put ( 3, 3 );
boolean ok = cache.size () == 4 || die ( "size" + cache.size () );
cache.put ( 4, 4 );
cache.put ( 5, 5 );
puts (cache);
ok |= cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 2 ) == 2 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
ok |= cache.getSilent ( 4 ) == 4 || die ();
ok |= cache.getSilent ( 5 ) == 5 || die ();
cache.get ( 2 );
cache.get ( 3 );
cache.put ( 6, 6 );
cache.put ( 7, 7 );
ok |= cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 2 ) == 2 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
cache.put ( 8, 8 );
cache.put ( 9, 9 );
ok |= cache.getSilent ( 4 ) == null || die ();
ok |= cache.getSilent ( 5 ) == null || die ();
puts (cache);
if ( !ok ) die ();
@Test
public void test2 ()
LruCache <Integer, Integer> cache = new LruSimpleConcurrentCache<> ( 400, false );
cache.put ( 0, 0 );
cache.put ( 1, 1 );
cache.put ( 2, 2 );
cache.put ( 3, 3 );
for (int index =0 ; index < 5_000; index++)
cache.get(0);
cache.get ( 1 );
cache.put ( 2, index );
cache.put ( 3, index );
cache.put(index, index);
boolean ok = cache.getSilent ( 0 ) == 0 || die ();
ok |= cache.getSilent ( 1 ) == 1 || die ();
ok |= cache.getSilent ( 2 ) != null || die ();
ok |= cache.getSilent ( 3 ) != null || die ();
ok |= cache.size () < 600 || die();
if ( !ok ) die ();
这是最后一篇文章。我删除的第一篇文章是 LFU 而不是 LRU 缓存。
我想我会再试一次。我试图使用标准 JDK 提供最简单的 LRU 缓存版本,但没有太多实现。
这是我想出的。我的第一次尝试有点灾难,因为我实现了 LFU 而不是 LRU,然后我添加了 FIFO 和 LRU 支持……然后我意识到它正在变成一个怪物。然后我开始和我几乎不感兴趣的朋友 John 交谈,然后我详细描述了我如何实现 LFU、LRU 和 FIFO,以及如何使用简单的 ENUM arg 切换它,然后我意识到我真正想要的只是是一个简单的 LRU。所以请忽略我之前的帖子,如果您想查看可通过枚举切换的 LRU/LFU/FIFO 缓存,请告诉我...不是吗?好的..他走了。
仅使用 JDK 的最简单的 LRU。我实现了并发版本和非并发版本。
我创建了一个通用界面(它是极简主义,因此可能缺少一些您想要的功能,但它适用于我的用例,但如果您想查看功能 XYZ 让我知道...我活着就是为了写作代码。)。
public interface LruCache<KEY, VALUE>
void put ( KEY key, VALUE value );
VALUE get ( KEY key );
VALUE getSilent ( KEY key );
void remove ( KEY key );
int size ();
您可能想知道 getSilent 是什么。我用它来测试。 getSilent 不会更改项目的 LRU 分数。
首先是非并发的......
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
public class LruCacheNormal<KEY, VALUE> implements LruCache<KEY,VALUE>
Map<KEY, VALUE> map = new HashMap<> ();
Deque<KEY> queue = new LinkedList<> ();
final int limit;
public LruCacheNormal ( int limit )
this.limit = limit;
public void put ( KEY key, VALUE value )
VALUE oldValue = map.put ( key, value );
/*If there was already an object under this key,
then remove it before adding to queue
Frequently used keys will be at the top so the search could be fast.
*/
if ( oldValue != null )
queue.removeFirstOccurrence ( key );
queue.addFirst ( key );
if ( map.size () > limit )
final KEY removedKey = queue.removeLast ();
map.remove ( removedKey );
public VALUE get ( KEY key )
/* Frequently used keys will be at the top so the search could be fast.*/
queue.removeFirstOccurrence ( key );
queue.addFirst ( key );
return map.get ( key );
public VALUE getSilent ( KEY key )
return map.get ( key );
public void remove ( KEY key )
/* Frequently used keys will be at the top so the search could be fast.*/
queue.removeFirstOccurrence ( key );
map.remove ( key );
public int size ()
return map.size ();
public String toString()
return map.toString ();
如果缓存很大,queue.removeFirstOccurrence 操作可能会很昂贵。可以以 LinkedList 为例,添加一个从元素到节点的反向查找哈希映射,以使删除操作更快更一致。我也开始了,但后来意识到我不需要它。但是……也许……
当 put 被调用时,键被添加到队列中。当 get 被调用时,键会被移除并重新添加到队列的顶部。
如果您的缓存很小并且构建项目的成本很高,那么这应该是一个很好的缓存。如果您的缓存真的很大,那么线性搜索可能是一个瓶颈,特别是如果您没有缓存的热点区域。热点越强烈,线性搜索越快,因为热点总是在线性搜索的顶部。无论如何...要加快速度,需要编写另一个具有删除操作的 LinkedList,该操作具有反向元素到节点查找以进行删除,然后删除将与从哈希映射中删除键一样快。
如果您的缓存少于 1,000 个项目,这应该可以正常工作。
这里有一个简单的测试来展示它的实际操作。
public class LruCacheTest
@Test
public void test ()
LruCache<Integer, Integer> cache = new LruCacheNormal<> ( 4 );
cache.put ( 0, 0 );
cache.put ( 1, 1 );
cache.put ( 2, 2 );
cache.put ( 3, 3 );
boolean ok = cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 0 ) == 0 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
cache.put ( 4, 4 );
cache.put ( 5, 5 );
ok |= cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 0 ) == null || die ();
ok |= cache.getSilent ( 1 ) == null || die ();
ok |= cache.getSilent ( 2 ) == 2 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
ok |= cache.getSilent ( 4 ) == 4 || die ();
ok |= cache.getSilent ( 5 ) == 5 || die ();
if ( !ok ) die ();
最后一个 LRU 缓存是单线程的,请不要将它包装在同步的任何东西中......
这是一个并发版本的尝试。
import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrentLruCache<KEY, VALUE> implements LruCache<KEY,VALUE>
private final ReentrantLock lock = new ReentrantLock ();
private final Map<KEY, VALUE> map = new ConcurrentHashMap<> ();
private final Deque<KEY> queue = new LinkedList<> ();
private final int limit;
public ConcurrentLruCache ( int limit )
this.limit = limit;
@Override
public void put ( KEY key, VALUE value )
VALUE oldValue = map.put ( key, value );
if ( oldValue != null )
removeThenAddKey ( key );
else
addKey ( key );
if (map.size () > limit)
map.remove ( removeLast() );
@Override
public VALUE get ( KEY key )
removeThenAddKey ( key );
return map.get ( key );
private void addKey(KEY key)
lock.lock ();
try
queue.addFirst ( key );
finally
lock.unlock ();
private KEY removeLast( )
lock.lock ();
try
final KEY removedKey = queue.removeLast ();
return removedKey;
finally
lock.unlock ();
private void removeThenAddKey(KEY key)
lock.lock ();
try
queue.removeFirstOccurrence ( key );
queue.addFirst ( key );
finally
lock.unlock ();
private void removeFirstOccurrence(KEY key)
lock.lock ();
try
queue.removeFirstOccurrence ( key );
finally
lock.unlock ();
@Override
public VALUE getSilent ( KEY key )
return map.get ( key );
@Override
public void remove ( KEY key )
removeFirstOccurrence ( key );
map.remove ( key );
@Override
public int size ()
return map.size ();
public String toString ()
return map.toString ();
主要区别在于使用 ConcurrentHashMap 而不是 HashMap,以及使用 Lock(我本可以使用同步,但是...)。
我还没有对它进行过测试,但它似乎是一个简单的 LRU 缓存,在 80% 的需要简单 LRU 映射的用例中可能会奏效。
我欢迎反馈,除了你为什么不使用库 a、b 或 c。 我不总是使用库的原因是因为我并不总是希望每个 war 文件都为 80MB,而且我编写库所以我倾向于使用足够好的解决方案使库可插入,并且有人可以插入- 如果他们愿意,可以在另一个缓存提供程序中。 :) 我永远不知道何时有人可能需要 Guava 或 ehcache 或其他我不想包含它们的东西,但如果我使缓存可插入,我也不会排除它们。
减少依赖有其自身的回报。我喜欢就如何使这更简单或更快或两者兼而有之获得一些反馈。
如果有人知道准备好了....
好吧..我知道你在想什么...他为什么不直接使用 LinkedHashMap 中的 removeEldest 条目,我应该但是.... 但是.. 但是.. 那将是 FIFO 而不是 LRU我们正在尝试实现 LRU。
Map<KEY, VALUE> map = new LinkedHashMap<KEY, VALUE> ()
@Override
protected boolean removeEldestEntry ( Map.Entry<KEY, VALUE> eldest )
return this.size () > limit;
;
上述代码测试失败...
cache.get ( 2 );
cache.get ( 3 );
cache.put ( 6, 6 );
cache.put ( 7, 7 );
ok |= cache.size () == 4 || die ( "size" + cache.size () );
ok |= cache.getSilent ( 2 ) == 2 || die ();
ok |= cache.getSilent ( 3 ) == 3 || die ();
ok |= cache.getSilent ( 4 ) == null || die ();
ok |= cache.getSilent ( 5 ) == null || die ();
所以这里是一个使用 removeEldestEntry 的快速且脏的 FIFO 缓存。
import java.util.*;
public class FifoCache<KEY, VALUE> implements LruCache<KEY,VALUE>
final int limit;
Map<KEY, VALUE> map = new LinkedHashMap<KEY, VALUE> ()
@Override
protected boolean removeEldestEntry ( Map.Entry<KEY, VALUE> eldest )
return this.size () > limit;
;
public LruCacheNormal ( int limit )
this.limit = limit;
public void put ( KEY key, VALUE value )
map.put ( key, value );
public VALUE get ( KEY key )
return map.get ( key );
public VALUE getSilent ( KEY key )
return map.get ( key );
public void remove ( KEY key )
map.remove ( key );
public int size ()
return map.size ();
public String toString()
return map.toString ();
FIFO 很快。不用四处寻找。您可以在 LRU 前面放置一个 FIFO,这样可以很好地处理大多数热条目。更好的 LRU 将需要反向元素到节点功能。
无论如何...既然我写了一些代码,让我看看其他答案,看看我错过了什么...我第一次扫描它们时。
【讨论】:
【参考方案4】:LinkedHashMap
是 O(1),但需要同步。无需在那里重新发明***。
增加并发性的2个选项:
1。
创建多个LinkedHashMap
,并散列到它们中:
例如:LinkedHashMap[4], index 0, 1, 2, 3
。在键上执行key%4
(或binary OR
上[key, 3]
)来选择要执行放置/获取/删除的地图。
2。
你可以通过扩展ConcurrentHashMap
来做一个“几乎”的 LRU,并在其中的每个区域中都有一个类似结构的链接哈希映射。锁定将比同步的LinkedHashMap
更细化。在put
或putIfAbsent
上,只需要锁定列表的头部和尾部(每个区域)。在删除或获取整个区域时需要锁定。我很好奇某种原子链表是否会在这里有所帮助——对于列表的头部可能是这样。也许更多。
该结构不会保留总顺序,而只会保留每个区域的顺序。只要条目的数量远大于区域的数量,这对于大多数缓存来说就足够了。每个区域都必须有自己的条目计数,这将被使用而不是驱逐触发器的全局计数。
ConcurrentHashMap
中的默认区域数为 16,这对于当今的大多数服务器来说已经足够了。
在中等并发下会更容易编写和更快。
会更难编写,但在非常高的并发性下可以更好地扩展。正常访问会更慢(就像ConcurrentHashMap
比没有并发的HashMap
慢)
【讨论】:
【参考方案5】:有两种开源实现。
Apache Solr 有 ConcurrentLRUCache:https://lucene.apache.org/solr/3_6_1/org/apache/solr/util/ConcurrentLRUCache.html
有一个 ConcurrentLinkedHashMap 的开源项目: http://code.google.com/p/concurrentlinkedhashmap/
【讨论】:
Solr 的解决方案实际上并不是 LRU,但ConcurrentLinkedHashMap
很有趣。它声称已从 Guava 滚入MapMaker
,但我没有在文档中发现它。知道这项工作是怎么回事吗?
集成了简化版,但测试未完成,暂未公开。我在进行更深层次的集成时遇到了很多问题,但我确实希望完成它,因为有一些不错的算法属性。添加了侦听驱逐(容量、到期、GC)的能力,并且基于 CLHM 的方法(侦听器队列)。我也想贡献“加权值”的想法,因为这在缓存集合时很有用。不幸的是,由于其他承诺,我太忙了,无法投入 Guava 应得的时间(我向 Kevin/Charles 承诺过)。
更新:集成已完成并在 Guava r08 中公开。这通过#maximumSize() 设置。【参考方案6】:
我会考虑使用java.util.concurrent.PriorityBlockingQueue,优先级由每个元素中的“numberOfUses”计数器确定。我会非常非常小心让我的所有同步正确,因为“numberOfUses”计数器暗示该元素不能是不可变的。
元素对象将是缓存中对象的包装器:
class CacheElement
private final Object obj;
private int numberOfUsers = 0;
CacheElement(Object obj)
this.obj = obj;
... etc.
【讨论】:
你不是说必须是不可变的吗? 请注意,如果您尝试执行 steve mcleod 提到的priorityblockingqueue 版本,您应该使元素不可变,因为在队列中修改元素不会有任何影响,您需要删除该元素并重新添加它以重新确定优先级。 James 下面指出了我犯的一个错误。我提供的证据证明了编写可靠、坚固的缓存是多么困难。【参考方案7】:希望这会有所帮助。
import java.util.*;
public class Lru
public static <K,V> Map<K,V> lruCache(final int maxSize)
return new LinkedHashMap<K, V>(maxSize*4/3, 0.75f, true)
private static final long serialVersionUID = -3588047435434569014L;
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest)
return size() > maxSize;
;
public static void main(String[] args )
Map<Object, Object> lru = Lru.lruCache(2);
lru.put("1", "1");
lru.put("2", "2");
lru.put("3", "3");
System.out.println(lru);
【讨论】:
很好的例子!您能否评论一下为什么需要设置容量 maxSize*4/3? @Akvel 被称为初始容量,可以是任何 [integer] 值,而 0.75f 是默认负载因子,希望此链接有所帮助:ashishsharma.me/2011/09/custom-lru-cache-java.html【参考方案8】:LRU Cache 可以使用 ConcurrentLinkedQueue 和 ConcurrentHashMap 来实现,也可以在多线程场景中使用。队列的头部是在队列中时间最长的元素。队列的尾部是在队列中时间最短的元素。当 Map 中存在元素时,我们可以将其从 LinkedQueue 中移除并插入到尾部。
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class LRUCache<K,V>
private ConcurrentHashMap<K,V> map;
private ConcurrentLinkedQueue<K> queue;
private final int size;
public LRUCache(int size)
this.size = size;
map = new ConcurrentHashMap<K,V>(size);
queue = new ConcurrentLinkedQueue<K>();
public V get(K key)
//Recently accessed, hence move it to the tail
queue.remove(key);
queue.add(key);
return map.get(key);
public void put(K key, V value)
//ConcurrentHashMap doesn't allow null key or values
if(key == null || value == null) throw new NullPointerException();
if(map.containsKey(key)
queue.remove(key);
if(queue.size() >= size)
K lruKey = queue.poll();
if(lruKey != null)
map.remove(lruKey);
queue.add(key);
map.put(key,value);
【讨论】:
这是不是线程安全的。例如,您可以通过同时调用put
轻松超过最大 LRU 大小。
请更正。首先,它不会在线 map.containsKey(key) 上编译。其次,在 get() 中,您应该检查密钥是否真的被删除,否则 map 和 queue 变得不同步,并且“queue.size() >= size”始终为真。我将发布修复此问题的版本,因为我喜欢您使用这两个集合的想法。【参考方案9】:
这是我对 LRU 的实现。我使用了 PriorityQueue,它基本上可以作为 FIFO 而不是线程安全的。 使用的比较器基于页面时间创建并基于执行排序 最近最少使用时间的页面。
供考虑的页面:2、1、0、2、8、2、4
添加到缓存中的页面是:2 添加到缓存中的页面是:1 添加到缓存中的页面为:0 页面:2 已存在于缓存中。上次访问时间已更新 页面错误,页面:1,替换为 PAGE:8 添加到缓存中的页面为:8 页面:2 已存在于缓存中。上次访问时间已更新 页面错误,页面:0,替换为页面:4 添加到缓存中的页面为:4
输出
LRUCache 页面 ------------- 页面名称:8,页面创建时间:1365957019974 页面名称:2,页面创建时间:1365957020074 页面名称:4,页面创建时间:1365957020174
在此处输入代码
import java.util.Comparator;
import java.util.Iterator;
import java.util.PriorityQueue;
public class LRUForCache
private PriorityQueue<LRUPage> priorityQueue = new PriorityQueue<LRUPage>(3, new LRUPageComparator());
public static void main(String[] args) throws InterruptedException
System.out.println(" Pages for consideration : 2, 1, 0, 2, 8, 2, 4");
System.out.println("----------------------------------------------\n");
LRUForCache cache = new LRUForCache();
cache.addPageToQueue(new LRUPage("2"));
Thread.sleep(100);
cache.addPageToQueue(new LRUPage("1"));
Thread.sleep(100);
cache.addPageToQueue(new LRUPage("0"));
Thread.sleep(100);
cache.addPageToQueue(new LRUPage("2"));
Thread.sleep(100);
cache.addPageToQueue(new LRUPage("8"));
Thread.sleep(100);
cache.addPageToQueue(new LRUPage("2"));
Thread.sleep(100);
cache.addPageToQueue(new LRUPage("4"));
Thread.sleep(100);
System.out.println("\nLRUCache Pages");
System.out.println("-------------");
cache.displayPriorityQueue();
public synchronized void addPageToQueue(LRUPage page)
boolean pageExists = false;
if(priorityQueue.size() == 3)
Iterator<LRUPage> iterator = priorityQueue.iterator();
while(iterator.hasNext())
LRUPage next = iterator.next();
if(next.getPageName().equals(page.getPageName()))
/* wanted to just change the time, so that no need to poll and add again.
but elements ordering does not happen, it happens only at the time of adding
to the queue
In case somebody finds it, plz let me know.
*/
//next.setPageCreationTime(page.getPageCreationTime());
priorityQueue.remove(next);
System.out.println("Page: " + page.getPageName() + " already exisit in cache. Last accessed time updated");
pageExists = true;
break;
if(!pageExists)
// enable it for printing the queue elemnts
//System.out.println(priorityQueue);
LRUPage poll = priorityQueue.poll();
System.out.println("Page Fault, PAGE: " + poll.getPageName()+", Replaced with PAGE: "+page.getPageName());
if(!pageExists)
System.out.println("Page added into cache is : " + page.getPageName());
priorityQueue.add(page);
public void displayPriorityQueue()
Iterator<LRUPage> iterator = priorityQueue.iterator();
while(iterator.hasNext())
LRUPage next = iterator.next();
System.out.println(next);
class LRUPage
private String pageName;
private long pageCreationTime;
public LRUPage(String pagename)
this.pageName = pagename;
this.pageCreationTime = System.currentTimeMillis();
public String getPageName()
return pageName;
public long getPageCreationTime()
return pageCreationTime;
public void setPageCreationTime(long pageCreationTime)
this.pageCreationTime = pageCreationTime;
@Override
public boolean equals(Object obj)
LRUPage page = (LRUPage)obj;
if(pageCreationTime == page.pageCreationTime)
return true;
return false;
@Override
public int hashCode()
return (int) (31 * pageCreationTime);
@Override
public String toString()
return "PageName: " + pageName +", PageCreationTime: "+pageCreationTime;
class LRUPageComparator implements Comparator<LRUPage>
@Override
public int compare(LRUPage o1, LRUPage o2)
if(o1.getPageCreationTime() > o2.getPageCreationTime())
return 1;
if(o1.getPageCreationTime() < o2.getPageCreationTime())
return -1;
return 0;
【讨论】:
【参考方案10】:这是我测试过的最好的并发 LRU 缓存实现,没有任何同步块:
public class ConcurrentLRUCache<Key, Value>
private final int maxSize;
private ConcurrentHashMap<Key, Value> map;
private ConcurrentLinkedQueue<Key> queue;
public ConcurrentLRUCache(final int maxSize)
this.maxSize = maxSize;
map = new ConcurrentHashMap<Key, Value>(maxSize);
queue = new ConcurrentLinkedQueue<Key>();
/**
* @param key - may not be null!
* @param value - may not be null!
*/
public void put(final Key key, final Value value)
if (map.containsKey(key))
queue.remove(key); // remove the key from the FIFO queue
while (queue.size() >= maxSize)
Key oldestKey = queue.poll();
if (null != oldestKey)
map.remove(oldestKey);
queue.add(key);
map.put(key, value);
/**
* @param key - may not be null!
* @return the value associated to the given key or null
*/
public Value get(final Key key)
return map.get(key);
【讨论】:
@zoltan boda....您还没有处理过一种情况..如果同一个对象被多次使用怎么办?在这种情况下,我们不应该为同一个对象添加多个条目......相反,它的键应该是 警告:这不是 LRU 缓存。在 LRU 缓存中,您丢弃最近最少访问的项目。这个扔掉最近最少写的项目。执行 queue.remove(key) 操作也是线性扫描。 另外 ConcurrentLinkedQueue#size() 不是一个固定时间的操作。 您的 put 方法看起来并不安全 - 它有一些 check-then-act 语句会因多个线程而中断。【参考方案11】:这是我使用的 LRU 缓存,它封装了一个 LinkedHashMap 并使用一个简单的同步锁来处理并发,保护多汁点。它在使用时“接触”元素,使它们再次成为“最新鲜”的元素,因此它实际上是 LRU。我还要求我的元素具有最短寿命,您也可以将其视为允许的“最长空闲时间”,然后您将被驱逐。
不过,我同意 Hank 的结论并接受了答案——如果我今天重新开始,我会查看 Guava 的 CacheBuilder
。
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class MaxIdleLRUCache<KK, VV>
final static private int IDEAL_MAX_CACHE_ENTRIES = 128;
public interface DeadElementCallback<KK, VV>
public void notify(KK key, VV element);
private Object lock = new Object();
private long minAge;
private HashMap<KK, Item<VV>> cache;
public MaxIdleLRUCache(long minAgeMilliseconds)
this(minAgeMilliseconds, IDEAL_MAX_CACHE_ENTRIES);
public MaxIdleLRUCache(long minAgeMilliseconds, int idealMaxCacheEntries)
this(minAgeMilliseconds, idealMaxCacheEntries, null);
public MaxIdleLRUCache(long minAgeMilliseconds, int idealMaxCacheEntries, final DeadElementCallback<KK, VV> callback)
this.minAge = minAgeMilliseconds;
this.cache = new LinkedHashMap<KK, Item<VV>>(IDEAL_MAX_CACHE_ENTRIES + 1, .75F, true)
private static final long serialVersionUID = 1L;
// This method is called just after a new entry has been added
public boolean removeEldestEntry(Map.Entry<KK, Item<VV>> eldest)
// let's see if the oldest entry is old enough to be deleted. We don't actually care about the cache size.
long age = System.currentTimeMillis() - eldest.getValue().birth;
if (age > MaxIdleLRUCache.this.minAge)
if ( callback != null )
callback.notify(eldest.getKey(), eldest.getValue().payload);
return true; // remove it
return false; // don't remove this element
;
public void put(KK key, VV value)
synchronized ( lock )
// System.out.println("put->"+key+","+value);
cache.put(key, new Item<VV>(value));
public VV get(KK key)
synchronized ( lock )
// System.out.println("get->"+key);
Item<VV> item = getItem(key);
return item == null ? null : item.payload;
public VV remove(String key)
synchronized ( lock )
// System.out.println("remove->"+key);
Item<VV> item = cache.remove(key);
if ( item != null )
return item.payload;
else
return null;
public int size()
synchronized ( lock )
return cache.size();
private Item<VV> getItem(KK key)
Item<VV> item = cache.get(key);
if (item == null)
return null;
item.touch(); // idle the item to reset the timeout threshold
return item;
private static class Item<T>
long birth;
T payload;
Item(T payload)
this.birth = System.currentTimeMillis();
this.payload = payload;
public void touch()
this.birth = System.currentTimeMillis();
【讨论】:
【参考方案12】:对于缓存而言,您通常会通过代理对象(URL、字符串...)查找一些数据,因此在接口方面您将需要地图。但是要将事情踢出去,您需要一个类似结构的队列。在内部,我会维护两个数据结构,一个 Priority-Queue 和一个 HashMap。这是一个应该能够在 O(1) 时间内完成所有事情的实现。
这是我很快就完成的一门课程:
import java.util.HashMap;
import java.util.Map;
public class LRUCache<K, V>
int maxSize;
int currentSize = 0;
Map<K, ValueHolder<K, V>> map;
LinkedList<K> queue;
public LRUCache(int maxSize)
this.maxSize = maxSize;
map = new HashMap<K, ValueHolder<K, V>>();
queue = new LinkedList<K>();
private void freeSpace()
K k = queue.remove();
map.remove(k);
currentSize--;
public void put(K key, V val)
while(currentSize >= maxSize)
freeSpace();
if(map.containsKey(key))
//just heat up that item
get(key);
return;
ListNode<K> ln = queue.add(key);
ValueHolder<K, V> rv = new ValueHolder<K, V>(val, ln);
map.put(key, rv);
currentSize++;
public V get(K key)
ValueHolder<K, V> rv = map.get(key);
if(rv == null) return null;
queue.remove(rv.queueLocation);
rv.queueLocation = queue.add(key);//this ensures that each item has only one copy of the key in the queue
return rv.value;
class ListNode<K>
ListNode<K> prev;
ListNode<K> next;
K value;
public ListNode(K v)
value = v;
prev = null;
next = null;
class ValueHolder<K,V>
V value;
ListNode<K> queueLocation;
public ValueHolder(V value, ListNode<K> ql)
this.value = value;
this.queueLocation = ql;
class LinkedList<K>
ListNode<K> head = null;
ListNode<K> tail = null;
public ListNode<K> add(K v)
if(head == null)
assert(tail == null);
head = tail = new ListNode<K>(v);
else
tail.next = new ListNode<K>(v);
tail.next.prev = tail;
tail = tail.next;
if(tail.prev == null)
tail.prev = head;
head.next = tail;
return tail;
public K remove()
if(head == null)
return null;
K val = head.value;
if(head.next == null)
head = null;
tail = null;
else
head = head.next;
head.prev = null;
return val;
public void remove(ListNode<K> ln)
ListNode<K> prev = ln.prev;
ListNode<K> next = ln.next;
if(prev == null)
head = next;
else
prev.next = next;
if(next == null)
tail = prev;
else
next.prev = prev;
这是它的工作原理。密钥存储在一个链表中,最旧的密钥位于列表的前面(新密钥位于后面),因此当您需要“弹出”某些内容时,只需将其从队列前面弹出,然后使用密钥从地图中删除值。当一个项目被引用时,您从地图中获取 ValueHolder,然后使用 queuelocation 变量从队列中的当前位置删除键,然后将其放在队列的后面(它现在是最近使用的)。添加东西几乎是一样的。
我确定这里有很多错误,而且我还没有实现任何同步。但是这个类将提供 O(1) 添加到缓存,O(1) 删除旧项目和 O(1) 检索缓存项目。由于运行时,即使是微不足道的同步(仅同步每个公共方法)仍然很少有锁争用。如果有人有任何聪明的同步技巧,我会非常感兴趣。此外,我确信您可以使用 maxsize 变量对地图进行一些额外的优化。
【讨论】:
感谢您提供的详细程度,但这在哪里可以胜过LinkedHashMap
+ Collections.synchronizedMap()
实施?
性能,我不确定,但我不认为 LinkedHashMap 有 O(1) 插入(它可能 O(log(n))),实际上你可以添加一些方法在我的实现中完成地图接口,然后使用 Collections.synchronizedMap 添加并发。
LinkedList 类上面的 add 方法中有一个 else 块中的代码,即 if(tail.prev == null) tail.prev = head;头。下一个 = 尾;这段代码什么时候执行?我跑了几次试运行,我认为这永远不会被执行,应该被删除。【参考方案13】:
看看ConcurrentSkipListMap。它应该给你 log(n) 时间来测试和删除一个元素(如果它已经包含在缓存中),以及重新添加它的恒定时间。
您只需要一些计数器等和包装器元素来强制 LRU 顺序的排序,并确保在缓存已满时丢弃最近的内容。
【讨论】:
ConcurrentSkipListMap
会比ConcurrentHashMap
提供一些易于实施的好处,还是只是避免病态病例?
这会使事情变得更简单,因为 ConcurrentSkipListMap 对元素进行排序,这将允许您管理使用的顺序。ConcurrentHashMap 不这样做,因此您基本上必须遍历整个缓存内容以更新元素的“上次使用的计数器”或其他内容
因此,使用ConcurrentSkipListMap
实现,我将创建一个新的Map
接口实现,它委托给ConcurrentSkipListMap
并执行某种包装,以便将任意键类型包装在一个可以根据上次访问轻松排序的类型?【参考方案14】:
这是我的简短实现,请批评或改进!
package util.collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Limited size concurrent cache map implementation.<br/>
* LRU: Least Recently Used.<br/>
* If you add a new key-value pair to this cache after the maximum size has been exceeded,
* the oldest key-value pair will be removed before adding.
*/
public class ConcurrentLRUCache<Key, Value>
private final int maxSize;
private int currentSize = 0;
private ConcurrentHashMap<Key, Value> map;
private ConcurrentLinkedQueue<Key> queue;
public ConcurrentLRUCache(final int maxSize)
this.maxSize = maxSize;
map = new ConcurrentHashMap<Key, Value>(maxSize);
queue = new ConcurrentLinkedQueue<Key>();
private synchronized void freeSpace()
Key key = queue.poll();
if (null != key)
map.remove(key);
currentSize = map.size();
public void put(Key key, Value val)
if (map.containsKey(key)) // just heat up that item
put(key, val);
return;
while (currentSize >= maxSize)
freeSpace();
synchronized(this)
queue.add(key);
map.put(key, val);
currentSize++;
public Value get(Key key)
return map.get(key);
【讨论】:
这不是LRU缓存,只是FIFO缓存。【参考方案15】:这是我自己对这个问题的实现
simplelrucache 提供线程安全、非常简单、非分布式 LRU 缓存,支持 TTL。它提供了两种实现方式:
基于 ConcurrentLinkedHashMap 的并发 基于LinkedHashMap同步你可以在这里找到它:http://code.google.com/p/simplelrucache/
【讨论】:
【参考方案16】:最好的实现方式是使用保持元素插入顺序的LinkedHashMap。以下是示例代码:
public class Solution
Map<Integer,Integer> cache;
int capacity;
public Solution(int capacity)
this.cache = new LinkedHashMap<Integer,Integer>(capacity);
this.capacity = capacity;
// This function returns false if key is not
// present in cache. Else it moves the key to
// front by first removing it and then adding
// it, and returns true.
public int get(int key)
if (!cache.containsKey(key))
return -1;
int value = cache.get(key);
cache.remove(key);
cache.put(key,value);
return cache.get(key);
public void set(int key, int value)
// If already present, then
// remove it first we are going to add later
if(cache.containsKey(key))
cache.remove(key);
// If cache size is full, remove the least
// recently used.
else if (cache.size() == capacity)
Iterator<Integer> iterator = cache.keySet().iterator();
cache.remove(iterator.next());
cache.put(key,value);
【讨论】:
【参考方案17】:我正在寻找使用 Java 代码的更好的 LRU 缓存。您是否可以使用LinkedHashMap
和Collections#synchronizedMap
共享您的Java LRU 缓存代码?目前我正在使用LRUMap implements Map
并且代码运行良好,但是我在使用以下方法使用 500 个用户进行负载测试时得到了ArrayIndexOutofBoundException
。该方法将最近的对象移动到队列的前面。
private void moveToFront(int index)
if (listHead != index)
int thisNext = nextElement[index];
int thisPrev = prevElement[index];
nextElement[thisPrev] = thisNext;
if (thisNext >= 0)
prevElement[thisNext] = thisPrev;
else
listTail = thisPrev;
//old listHead and new listHead say new is 1 and old was 0 then prev[1]= 1 is the head now so no previ so -1
// prev[0 old head] = new head right ; next[new head] = old head
prevElement[index] = -1;
nextElement[index] = listHead;
prevElement[listHead] = index;
listHead = index;
get(Object key)
和put(Object key, Object value)
方法调用上面的moveToFront
方法。
【讨论】:
【参考方案18】:想对汉克给出的答案添加评论,但有些我无法做到-请将其视为评论
LinkedHashMap 也根据其构造函数中传递的参数维护访问顺序 它保持双行列表以保持顺序(参见 LinkedHashMap.Entry)
@Pacerier 如果再次添加元素,LinkedHashMap 在迭代时保持相同的顺序是正确的,但这仅适用于插入顺序模式。
这是我在 LinkedHashMap.Entry 对象的 java 文档中发现的
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap<K,V> m)
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder)
lm.modCount++;
remove();
addBefore(lm.header);
此方法负责将最近访问的元素移动到列表末尾。所以总而言之 LinkedHashMap 是实现 LRUCache 的最佳数据结构。
【讨论】:
【参考方案19】:另一个想法,甚至是一个使用Java的LinkedHashMap集合的简单实现。
LinkedHashMap 提供了 removeEldestEntry 方法,可以按照示例中提到的方式覆盖。默认情况下,这个集合结构的实现是假的。如果该结构的真实性和大小超出初始容量,则将删除最旧或更旧的元素。
在我的情况下,我们可以有一个 pageno 和页面内容 pageno 是整数,而 pagecontent 我保留了页码值字符串。
import java.util.LinkedHashMap; import java.util.Map; /** * @author Deepak Singhvi * */ public class LRUCacheUsingLinkedHashMap private static int CACHE_SIZE = 3; public static void main(String[] args) System.out.println(" Pages for consideration : 2, 1, 0, 2, 8, 2, 4,99"); System.out.println("----------------------------------------------\n"); // accessOrder is true, so whenever any page gets changed or accessed, // its order will change in the map, LinkedHashMap<Integer,String> lruCache = new LinkedHashMap<Integer,String>(CACHE_SIZE, .75F, true) private static final long serialVersionUID = 1L; protected boolean removeEldestEntry(Map.Entry<Integer,String> eldest) return size() > CACHE_SIZE; ; lruCache.put(2, "2"); lruCache.put(1, "1"); lruCache.put(0, "0"); System.out.println(lruCache + " , After first 3 pages in cache"); lruCache.put(2, "2"); System.out.println(lruCache + " , Page 2 became the latest page in the cache"); lruCache.put(8, "8"); System.out.println(lruCache + " , Adding page 8, which removes eldest element 2 "); lruCache.put(2, "2"); System.out.println(lruCache+ " , Page 2 became the latest page in the cache"); lruCache.put(4, "4"); System.out.println(lruCache+ " , Adding page 4, which removes eldest element 1 "); lruCache.put(99, "99"); System.out.println(lruCache + " , Adding page 99, which removes eldest element 8 ");
以上代码执行结果如下:
Pages for consideration : 2, 1, 0, 2, 8, 2, 4,99
--------------------------------------------------
2=2, 1=1, 0=0 , After first 3 pages in cache
2=2, 1=1, 0=0 , Page 2 became the latest page in the cache
1=1, 0=0, 8=8 , Adding page 8, which removes eldest element 2
0=0, 8=8, 2=2 , Page 2 became the latest page in the cache
8=8, 2=2, 4=4 , Adding page 4, which removes eldest element 1
2=2, 4=4, 99=99 , Adding page 99, which removes eldest element 8
【讨论】:
这是一个先进先出。他要了一个 LRU。 它没有通过这个测试... cache.get ( 2 );缓存.get ( 3 );缓存.put ( 6, 6 );缓存.put ( 7, 7 ); ok |= cache.size () == 4 ||死(“大小”+ cache.size());好的 |= cache.getSilent ( 2 ) == 2 ||死 ();好的 |= cache.getSilent ( 3 ) == 3 ||死 ();好的 |= cache.getSilent ( 4 ) == null ||死 ();好的 |= cache.getSilent ( 5 ) == null ||死();【参考方案20】:遵循@sanjanab 概念(但在修复之后),我制作了我的 LRUCache 版本,还提供了消费者,允许在需要时对已删除的项目执行某些操作。
public class LRUCache<K, V>
private ConcurrentHashMap<K, V> map;
private final Consumer<V> onRemove;
private ConcurrentLinkedQueue<K> queue;
private final int size;
public LRUCache(int size, Consumer<V> onRemove)
this.size = size;
this.onRemove = onRemove;
this.map = new ConcurrentHashMap<>(size);
this.queue = new ConcurrentLinkedQueue<>();
public V get(K key)
//Recently accessed, hence move it to the tail
if (queue.remove(key))
queue.add(key);
return map.get(key);
return null;
public void put(K key, V value)
//ConcurrentHashMap doesn't allow null key or values
if (key == null || value == null) throw new IllegalArgumentException("key and value cannot be null!");
V existing = map.get(key);
if (existing != null)
queue.remove(key);
onRemove.accept(existing);
if (map.size() >= size)
K lruKey = queue.poll();
if (lruKey != null)
V removed = map.remove(lruKey);
onRemove.accept(removed);
queue.add(key);
map.put(key, value);
【讨论】:
【参考方案21】:android 提供了LRU Cache 的实现。 code 简洁明了。
【讨论】:
以上是关于你将如何在 Java 中实现 LRU 缓存?的主要内容,如果未能解决你的问题,请参考以下文章