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


169

请不要说EHCache或OSCache等。出于这个问题的目的,假设我想仅使用SDK来实现自己的实现(边做边学)。考虑到缓存将在多线程环境中使用,您将使用哪些数据结构?我已经使用LinkedHashMapCollections#synchronizedMap实现了一个,但是我很好奇是否有任何新的并发集合会更好。

更新:当我发现这个块时,我只是在阅读Yegge的最新文章

如果您需要固定时间的访问权限并希望保持插入顺序,那么做一个比一个真正出色的数据结构LinkedHashMap更好的选择。可能更妙的唯一方法是如果有并发版本。可惜。

在进行上面提到的LinkedHashMap+ Collections#synchronizedMap实现之前,我在想几乎完全相同的事情。很高兴知道我不仅忽略了某些东西。

根据到目前为止的答案,听起来对于高并发LRU来说,我最好的选择是使用一些使用相同的逻辑来扩展ConcurrentHashMapLinkedHashMap



Answers:


102

我喜欢其中许多建议,但现在我认为我会坚持使用LinkedHashMap+ Collections.synchronizedMap。如果以后再讨论这个问题,我可能会ConcurrentHashMapLinkedHashMapextends 相同的方式进行扩展HashMap

更新:

根据要求,这是我当前实现的要点。

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));

15
但是,我想在这里使用封装而不是继承。这是我从有效Java中学到的。
卡皮尔D

10
@KapilD已经有一段时间了,但是我几乎肯定JavaDocs LinkedHashMap明确支持此方法来创建LRU实现。
Hank Gay

7
@HankGay Java的LinkedHashMap(第三个参数= true)不是LRU缓存。这是因为重新输入条目不会影响条目的顺序(实际的LRU缓存会将最后插入的条目放在迭代顺序的后面,而不管该条目最初是否存在于缓存中)
Pacerier

2
@Pacerier我根本看不到这种行为。启用accessOrder映射后,所有操作都将输入一个最近使用(最新)的条目:初始插入,值更新和值检索。我想念什么吗?
Esailija

3
@Pacerier“重新输入条目不会影响条目的顺序”,这是不正确的。如果查看LinkedHashMap的实现,那么对于“ put”方法,它将继承自HashMap的实现。HashMap的Javadoc说:“如果映射先前包含键的映射,则替换旧值”。而且,如果您签出其源代码,则在替换旧值时,它将调用recordAccess方法,在LinkedHashMap的recordAccess方法中,它看起来像这样:if(lm.accessOrder){lm.modCount ++; 去掉(); addBefore(lm.header);}
尼龙


10

这是第二回合。

第一轮是我想出的,然后我重新阅读了该领域的评论,这在我的脑海中更加根深蒂固。

因此,这是带有单元测试的最简单版本,表明它可以在其他版本上运行。

首先是非并行版本:

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标志将跟踪获取和看跌的访问。请参阅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支持时,我的第一个尝试是个灾难。然后我意识到它正在变成一个怪物。然后,我开始与没兴趣的好友约翰交谈,然后我详细介绍了如何实现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,该操作具有与要查找的节点查找相反的元素,然后删除将与从哈希映射中删除键一样快。

如果您的缓存少于1000个项目,则应该可以解决。

这是一个简单的测试,以显示其实际操作。

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(我本来可以避免使用sync,但是...)。

我还没有对它进行过测试,但是它看起来像一个简单的LRU缓存,在需要简单的LRU映射的80%用例中,它可能会起作用。

我欢迎您提供反馈,但您为什么不使用库a,b或c。我不总是使用库的原因是因为我并不总是希望每个war文件都为80MB,所以我编写了库,因此我倾向于使libs可插入并提供足够好的解决方案,并且有人可以插入-如果愿意,可以在另一个缓存提供程序中使用。:)我从不知道何时有人可能需要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速度很快。没有搜索。您可以将FIFO放在LRU的前面,这样可以很好地处理大多数热门条目。更好的LRU将需要与Node功能相反的元素。

无论如何...现在我写了一些代码,让我浏览其他答案,看看我错过了什么...第一次扫描它们。


9

LinkedHashMap是O(1),但需要同步。无需在那里重新发明轮子。

增加并发性的2个选项:

1.创建多个LinkedHashMap,然后将它们散列到其中:示例:LinkedHashMap[4], index 0, 1, 2, 3。在键上执行key%4 (或binary OR在上[key, 3])以选择要放置/获取/删除的地图。

2.您可以通过扩展ConcurrentHashMap,并在其内部的每个区域中使用类似结构的链接哈希图来执行“几乎” LRU 。锁定将比LinkedHashMap同步的锁定更精细。上的putputIfAbsent仅在头和列表的尾部的锁定(每个区域)是必要的。在删除或获取整个区域时需要锁定。我很好奇,某种形式的原子链表在这里是否可以帮上忙-列表头可能也是如此。也许更多。

该结构将不保留总订单,而仅保留每个区域的订单。只要条目的数量远大于区域的数量,这对于大多数缓存就足够了。每个区域都必须有自己的条目计数,将使用此计数,而不是将其用于驱逐触发器的全局计数。a中的默认区域数ConcurrentHashMap是16,对于当今的大多数服务器来说已经足够了。

  1. 在适度并发的情况下将更容易编写并且运行更快。

  2. 编写起来会更加困难,但是在非常高的并发下可以更好地扩展。这将是正常访问慢(就像ConcurrentHashMap是慢HashMap在没有并发)


8

有两种开源实现。

Apache Solr具有ConcurrentLRUCache:https : //lucene.apache.org/solr/3_6_1/org/apache/solr/util/ConcurrentLRUCache.html

有一个ConcurrentLinkedHashMap的开源项目:http : //code.google.com/p/concurrentlinkedhashmap/


2
Solr的解决方案实际上不是LRU,但ConcurrentLinkedHashMap很有趣。它声称是MapMaker从Guava 引入的,但是我没有在文档中发现它。知道这是怎么回事吗?
汉克·盖伊

3
集成了简化版本,但是测试尚未完成,因此尚未公开。在进行更深入的集成时,我遇到了很多问题,但是由于有一些不错的算法属性,我希望完成它。添加了听取逐出(容量,到期时间,GC)的功能,该功能基于CLHM的方法(侦听器队列)。我也想贡献“加权值”的概念,因为这在缓存集合时很有用。不幸的是,由于其他承诺,我太忙了,无法投入番石榴应得的时间(我答应了凯文/查尔斯)。
本·马内斯

3
更新:集成已完成并在Guava r08中公开。通过#maximumSize()设置。
Ben Manes 2010年

7

我会考虑使用java.util.concurrent.PriorityBlockingQueue,其优先级由每个元素中的“ numberOfUses”计数器确定。我将非常非常小心地确保所有同步正确,因为“ numberOfUses”计数器表示该元素不能是不变的。

元素对象将是缓存中对象的包装器:

class CacheElement {
    private final Object obj;
    private int numberOfUsers = 0;

    CacheElement(Object obj) {
        this.obj = obj;
    }

    ... etc.
}

您是不是必须一成不变?
shsteimer

2
请注意,如果您尝试执行由史蒂夫·麦克劳德(steve mcleod)提到的优先级阻塞队列版本,则应使该元素不可变,因为在队列中修改该元素不会产生任何影响,因此您需要删除该元素并重新添加它,以便重新设置优先级。
james

詹姆斯在下面指出了我犯的一个错误。我提供的证据表明,编写可靠,坚固的缓存有多么困难。
史蒂夫·麦克劳德

6

希望这可以帮助 。

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);
}
}

1
很好的例子!您能否评论为什么需要设置容量maxSize * 4/3?
Akvel 2014年

1
@Akvel称为初始容量,可以是任何[整数]值,而默认值为0.75f,希望此链接有所
修改

5

可以使用ConcurrentLinkedQueue和ConcurrentHashMap来实现LRU缓存,它们也可以在多线程方案中使用。队列的开头是已在队列中停留最长时间的元素。队列的尾部是最短时间出现在队列中的元素。当地图中存在某个元素时,我们可以将其从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);
  }

}

不是线程安全的。例如,您可以通过同时调用轻松超过最大LRU大小put
dpeacock 2015年

请更正。首先,它不会在map.containsKey(key)行上编译。其次,在get()中,您应该检查键是否已真正删除,否则映射和队列不同步,并且“ queue.size()> = size”始终为真。我将发布修正的版本,因为我喜欢您使用这两个集合的想法。
Aleksander Lech

3

这是我对LRU的实现。我使用了PriorityQueue,它基本上是作为FIFO而不是线程安全的。基于页面时间创建并基于的使用比较器,以最近最少使用时间对页面进行排序。

供考虑的页面:2,1,0,2,8,2,4

添加到缓存中的
页面是:2 添加到缓存中的
页面是:1 添加到缓存中的
页面是:0 页面:2已存在于缓存中。上次访问时间已更新
Page Fault,PAGE:1,由PAGE:取代:8已
添加到缓存中的
页面是:8 页面:2已存在于缓存中。上次访问时间已更新
Page Fault,PAGE:0,用PAGE:4代替已
添加到高速缓存中的页面是:4

输出值

LRUCache页面
-----------
PageName:8,PageCreationTime:1365957019974
PageName:2,2,PageCreationTime:1365957020074
PageName:4,4,PageCreationTime: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;
    }
}

2

这是我经过测试的性能最好的并发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);
}

}


1
@zoltan boda ....您尚未处理一种情况..如果同一对象多次使用该怎么办?在这种情况下,我们不应为同一个对象添加多个条目...相反,其键应为

5
警告:这不是LRU缓存。在LRU缓存中,您丢弃了最近最少访问的项目。这将丢弃最近最少写的项目。这也是执行queue.remove(key)操作的线性扫描。
戴夫·L.

另外,ConcurrentLinkedQueue#size()也不是恒定时间操作。
NateS

3
您的put方法看起来并不安全-它具有一些check-then-act语句,这些语句将在多个线程中中断。
assylias

2

这是我使用的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();
        }
    }

}

2

对于高速缓存,通常您将通过代理对象(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))),实际上您可以在实现中添加一些方法来完成map接口然后使用Collections.synchronizedMap添加并发。
卢克

在上面add方法中的LinkedList类中,else块中有一个代码,即if(tail.prev == null){tail.prev = head; head.next =尾巴; 这个代码什么时候执行?我进行了几次试运行,我认为这将永远不会执行,应该删除。
Dipesh

1

看看ConcurrentSkipListMap。如果元素已经包含在缓存中,它应该给您log(n)时间来测试和删除该元素,并为您提供重新添加它的恒定时间。

您只需要一些计数器等和wrapper元素即可强制对LRU顺序进行排序,并确保在缓存已满时丢弃最近的东西。


相对于会ConcurrentSkipListMap提供一些易于实现的好处ConcurrentHashMap,还是仅仅是避免出现病理情况的情况?
汉克·盖伊

就像ConcurrentSkipListMap订购元素一样,这将使事情变得更简单,这将使您能够管理所使用的订购商品。ConcurrentHashMap不会执行此操作,因此,您基本上必须遍历整个缓存内容以更新元素的“ last”。二手计数器”或其他功能
madlep

因此,对于该ConcurrentSkipListMap实现,我将创建Map接口的新实现,该接口委派给该接口ConcurrentSkipListMap并执行某种包装,以便将任意键类型包装为易于根据上次访问进行排序的类型?
汉克·盖伊

1

这是我的简短实现,请批评或改善它!

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);
}
}

1
这不是LRU缓存,而是FIFO缓存。
lslab

1

这是我对这个问题的实现

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

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

您可以在这里找到它:http : //code.google.com/p/simplelrucache/


1

最好的实现方法是使用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);
}

}


0

我正在寻找使用Java代码的更好的LRU缓存。您是否可以使用LinkedHashMap和共享Java LRU缓存代码Collections#synchronizedMap?目前,我正在使用LRUMap implements Map并且代码可以正常工作,但是我正在ArrayIndexOutofBoundException使用以下方法使用500位用户进行负载测试。该方法将最近的对象移到队列的前面。

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方法。


0

想要在Hank给出的答案中添加评论,但是我无法做到这一点-请将其视为评论

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的最佳数据结构。


0

另一个想法,甚至是使用Java的LinkedHashMap集合的简单实现。

LinkedHashMap提供的方法removeEldestEntry可以用示例中提到的方式覆盖。默认情况下,此收集结构的实现为false。如果此结构的真实大小超出了初始容量,则将删除最老或更旧的元素。

在我的情况下,我们可以有一个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 

那是一个FIFO。他要求一个LRU。
RickHigh

它没有通过此测试... cache.get(2); cache.get(3); cache.put(6,6); cache.put(7,7); 好的| = cache.size()== 4 || die(“ size” + cache.size()); 好的| = cache.getSilent(2)== 2 || 死 (); 好的| = cache.getSilent(3)== 3 || 死 (); 好的| = cache.getSilent(4)==空|| 死 (); 好的| = cache.getSilent(5)==空|| 死 ();
RickHigh

0

遵循@sanjanab概念(但经过修复),我制作了LRUCache版本,还提供了Consumer,该Consumer允许根据需要对删除的项目执行某些操作。

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);
    }
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.