最佳实现LRU缓存的方法

16

我在研究LRU缓存实现的问题,当缓存达到最大容量时,最近最少使用的元素会被弹出,并用新元素替换它。

我有两种实现方法:

1). 创建两个类似于下面这样的映射表

std::map<timestamp, k> time_to_key
std::map<key, std::pair<timestamp, V>> LRUCache
为了插入一个新元素,我们可以将当前时间戳和值放入LRUCache中。当缓存的大小达到上限时,我们可以通过在time_to_key中找到最小的时间戳并移除相应的键来剔除最近最少使用的元素。 插入新项的复杂度为O(1),更新时间戳的复杂度为O(n)(因为我们需要在time_to_key中查找与时间戳对应的键)。
另一种方法是使用链接列表,在其中最近最少使用的缓存位于头部并且新项添加到尾部。当已经存在于缓存中的项到达时,与该项对应的节点将被移动到列表的尾部。 插入新项的复杂度为O(1),更新时间戳的复杂度也为O(n)(因为我们需要移动到列表的尾部),删除元素的复杂度为O(1)。
现在我有以下问题: 1.这两种实现哪一种更适合LRUCache? 2.是否还有其他实现LRU Cache的方式? 3.在Java中,我应该使用HashMap来实现LRUCache吗? 4.我看到过实现通用LRU缓存的问题,也看到过实现LRU缓存的问题。通用LRU缓存与LRU缓存有什么不同吗?
另一种在Java中实现LRUCache的最简单方法是使用LinkedHashMap,并覆盖boolean removeEldestEntry(Map.entry eldest)函数。

请参阅https://dev59.com/Z3VC5IYBdhLWcg3wpjB8。 - Ben Hocking
在Java中,你应该使用LinkedHashMap而不是HashMap。最好的LRU映射使用一些带有链接节点的Map形式,例如可以使用二叉搜索红黑树和链接(prev/next)节点以及parent/left/right/value/red|black字段来实现。或者说LinkedHashMap是基于树形桶的,带有prev/next节点。 - bestsss
Java 中的 HashMap、HashTable 和 LinkedHashMap 是使用哈希表实现的,即使用数组和线性探测来解决冲突。它们不是使用红黑树实现的。 - Amm Sokun
我从未说过它们是红黑树,你可以看到TreeMap,将prev/next添加到节点并不难,我有一个类似的映射用于原始双精度数,即红黑树w / prev / next在节点之间。 - bestsss
添加树形数据结构中的前/后节点并不难,但我想知道如果您想要添加前/后节点,是否需要修改内部TreeMap类? - Amm Sokun
9个回答

28

如果你想要一个LRU缓存,Java中最简单的方法是使用LinkedHashMap。默认行为是FIFO(先进先出),但是你可以将其更改为"访问顺序",使其成为一个LRU缓存。

public static <K,V> Map<K,V> lruCache(final int maxSize) {
    return new LinkedHashMap<K, V>(maxSize*4/3, 0.75f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > maxSize;
        }
    };
}

注意:我使用了构造函数,它将集合从最新的元素放在前面变成了最近使用的元素放在前面。

来自Javadoc

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder)
Constructs an empty LinkedHashMap instance with the specified initial capacity, load factor and ordering mode.
Parameters:
initialCapacity - the initial capacity
loadFactor - the load factor
accessOrder - the ordering mode - true for access-order, false for insertion-order

当accessOrder是true时,每次使用不是最后一个的条目的get()方法时,LinkedHashMap都会重新排列映射的顺序。

这样,最老的条目就是最近使用的。


@NikhilVerma 默认的负载因子为0.75,将其调高可能会导致效率低下。当负载因子为0.75时,您需要一个容量为 cacheSize/0.75 or cacheSize*4/3 的缓存,否则地图可能会重新调整大小以确保不超过其负载因子。即容量 * 负载因子 >= 大小()。 - Peter Lawrey
但是 HashMap 不需要重新调整大小吗?因为我们改变原始大小为(例如4)的数组中的元素? - Nikhil Verma
@NikhilVerma 当大小>容量*0.75时,它会调整大小,因此如果您想避免调整大小,则容量> = size / 0.75。 - Peter Lawrey
@PeterLawrey:我想问一下,实现线程安全的LRU缓存最简单和高效的方法是什么?使用读写锁与HashMap/双向链表结合吗? - Sahil Gupta
在Clojure中:(let [size (/ (* max-size 4) 3) p (proxy [java.util.LinkedHashMap] [size,0.75,true] (removeEldestEntry [e] (> (proxy-super size) size)))] ...) - Jonathan Leonard
显示剩余7条评论

2

我希望能够提供两种可能的实现方案来进一步解释这些建议。其中一种不是线程安全的,另一种可能是线程安全的。

下面是一个最简单的版本,并带有一个单元测试来展示它的工作原理。

首先是非并发版本:

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


}

真正的标记将跟踪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 ();

    }

现在我们来说一下并发版本...
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 ();
    }


}

我先讲解一下非并发版本的原因。上述代码尝试创建一些条纹以减少锁竞争。因此,它会对键进行哈希处理,然后查找该哈希值以找到实际的缓存。这使得限制大小更像是一个建议/大致猜测,具体取决于您的密钥哈希算法分布情况而有一定的误差。


2

通常,LRU缓存被表示为LIFO结构 - 单个元素队列。如果您的标准提供的LRU缓存不允许您从中间删除对象,例如将它们放在顶部,则可能需要自己编写。


更新时间戳的时间复杂度能否从O(n)优化到常数级别? - Amm Sokun
是的,它可以是O(1),请看我上面的评论,Java中的LinkedHashMap是O(1)。 - bestsss
LinkedHashMap 提供了一种遍历映射中存在的键的方法,但是查找最近最少使用的键/值对仍需要顺序搜索。 - Amm Sokun
@Amm,第一个(要删除的)是O(1)。map.entrySet().iterator().next()或者你可以重写removeEldestEntry(Map.Entry<K,V> eldest),LinkedHashMap不提供尾部(即最后添加/触摸)和反向遍历,尽管这在实际上由结构支持。 - bestsss
如果需要删除的项在列表中间,则不需要到达该元素才能进行删除吗? - Amm Sokun

2
考虑到缓存允许并发访问,良好设计LRU缓存的问题就在于:
a) 在更新两个结构(缓存结构和LRU结构)时,我们能否避免使用互斥锁。
b) 缓存的读取(获取)操作是否需要互斥锁?
更详细地说:假设我们使用java.util.concurrent.ConcurrentHashMap(缓存结构)和java.util.concurrent.ConcurrentLinkedQueue(LRU结构)实现此操作
a) 在编辑操作addEntry()、removeEntry()、evictEntries()等时,锁定这两个结构。
b) 以上可能会导致写入操作变慢,但是读取(获取)操作也需要在这两个结构上加锁。因为获取将意味着将条目放在LRU策略的队列前面。(假设条目从队列末尾删除)。
使用高效的并发结构,如ConcurrentHashMap和无等待的ConcurrentLinkedQueue,然后在它们上面加锁,就失去了它们的全部意义。
我用相同的方法实现了一个LRU缓存,但是LRU结构是异步更新的,消除了在访问这些结构时使用任何互斥锁的必要。LRU是缓存的内部细节,可以以任何方式实现,而不影响缓存的用户。
后来,我也了解了ConcurrentLinkedHashMap。

https://code.google.com/p/concurrentlinkedhashmap/

并发现它也试图做同样的事情。虽然没有使用过这个结构,但也许会很合适。

1
LinkedHashMap允许您覆盖removeEldestEntry函数,因此每当执行put时,您可以指定是否删除最旧的条目,从而实现LRU。
myLRUCache = new LinkedHashMap<Long,String>() {
protected boolean removeEldestEntry(Map.Entry eldest) 
{ 
if(this.size()>1000)
    return true;
  else
      return false;
}
}; 

1

问题陈述:

创建LRU缓存并存储最多5个员工对象,找出谁先登录,然后是谁...

package com.test.example.dto;

import java.sql.Timestamp;
/**
 * 
 * @author Vaquar.khan@gmail.com
 *
 */
public class Employee implements Comparable<Employee> {
    private int     id;
    private String  name;
    private int     age;
    private Timestamp loginTime ;

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public Timestamp getLoginTime() {
    return loginTime;
}

public void setLoginTime(Timestamp loginTime) {
    this.loginTime = loginTime;
}

@Override
public String toString() {
    return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", loginTime=" + loginTime + "]";
}

Employee(){}

public Employee(int id, String name, int age, Timestamp loginTime) {
    super();
    this.id = id;
    this.name = name;
    this.age = age;
    this.loginTime = loginTime;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + age;
    result = prime * result + id;
    result = prime * result + ((loginTime == null) ? 0 : loginTime.hashCode());
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Employee other = (Employee) obj;
    if (age != other.age) return false;
    if (id != other.id) return false;
    if (loginTime == null) {
        if (other.loginTime != null) return false;
    } else if (!loginTime.equals(other.loginTime)) return false;
    if (name == null) {
        if (other.name != null) return false;
    } else if (!name.equals(other.name)) return false;
    return true;
}

@Override
public int compareTo(Employee emp) {
    if (emp.getLoginTime().before( this.loginTime) ){
        return 1;
    } else if (emp.getLoginTime().after(this.loginTime)) {
        return -1;
    }
    return 0;
}


}

LRUObjectCacheExample
package com.test.example;

import java.sql.Timestamp;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.test.example.dto.Employee;
/**
 * 
 * @author Vaquar.khan@gmail.com
 *
 */
public class LRUObjectCacheExample {


    LinkedHashMap<Employee, Boolean>    lruCacheLinkedQueue;

public LRUObjectCacheExample(int capacity) {
    lruCacheLinkedQueue = new LinkedHashMap<Employee, Boolean>(capacity, 1.0f, true) {
        /**
         * 
         */
        private static final long   serialVersionUID    = 1L;

        @Override
        protected boolean removeEldestEntry(
                //calling map's entry method
                Map.Entry<Employee, Boolean> eldest) {
            return this.size() > capacity;
        }
    };
}

void addDataIntoCache(Employee employee) {
    lruCacheLinkedQueue.put(employee, true);
    displayLRUQueue();
}

boolean checkIfDataPresentIntoLRUCaache(int data) {
    return lruCacheLinkedQueue.get(data) != null;
}

 void deletePageNo(int data) {
    if (lruCacheLinkedQueue.get(data) != null){
            lruCacheLinkedQueue.remove(data);
    }
    displayLRUQueue();
}

 void displayLRUQueue() {
    System.out.print("-------------------------------------------------------"+"\n");
    System.out.print("Data into LRU Cache : ");
    for (Entry<Employee, Boolean> mapEntry : lruCacheLinkedQueue.entrySet()) {
        System.out.print("[" + mapEntry.getKey() + "]");
    }
    System.out.println("");
}

public static void main(String args[]) {
    Employee employee1 = new Employee(1,"Shahbaz",29, getCurrentTimeStamp());
    Employee employee2 = new Employee(2,"Amit",35,getCurrentTimeStamp());
    Employee employee3 = new Employee(3,"viquar",36,getCurrentTimeStamp());
    Employee employee4 = new Employee(4,"Sunny",20,getCurrentTimeStamp());
    Employee employee5 = new Employee(5,"sachin",28,getCurrentTimeStamp());
    Employee employee6 = new Employee(6,"Sneha",25,getCurrentTimeStamp());
    Employee employee7 = new Employee(7,"chantan",19,getCurrentTimeStamp());
    Employee employee8 = new Employee(8,"nitin",22,getCurrentTimeStamp());
    Employee employee9 = new Employee(9,"sanuj",31,getCurrentTimeStamp());
    //
    LRUObjectCacheExample lru = new LRUObjectCacheExample(5);
    lru.addDataIntoCache(employee5);//sachin
    lru.addDataIntoCache(employee4);//Sunny
    lru.addDataIntoCache(employee3);//viquar
    lru.addDataIntoCache(employee2);//Amit
    lru.addDataIntoCache(employee1);//Shahbaz -----capacity reached
    //
        lru.addDataIntoCache(employee6);/Sneha
        lru.addDataIntoCache(employee7);//chantan
        lru.addDataIntoCache(employee8);//nitin
        lru.addDataIntoCache(employee9);//sanuj
        //
        lru.deletePageNo(3);
        lru.deletePageNo(4);

    }
    private static Timestamp getCurrentTimeStamp(){
        return new java.sql.Timestamp(Calendar.getInstance().getTime().getTime());
    }

}

Results:

**Data into LRU Cache :** 
[Employee [id=1, name=Shahbaz, age=29, loginTime=2015-10-15 18:47:28.1
[Employee [id=6, name=Sneha, age=25, loginTime=2015-10-15 18:47:28.125
[Employee [id=7, name=chantan, age=19, loginTime=2015-10-15 18:47:28.125
[Employee [id=8, name=nitin, age=22, loginTime=2015-10-15 18:47:28.125
[Employee [id=9, name=sanuj, age=31, loginTime=2015-10-15 18:47:28.125

0

为了实现O(1)访问,我们需要使用哈希表,并使用双向链表来维护顺序。

基本算法是-从页面号码中,我们可以使用哈希表找到对应的DLL节点。如果页面存在,则将节点移动到DLL头部;否则将节点插入到DLL和哈希表中。如果DLL的大小已满,则可以从尾部删除最近最少使用的节点。

以下是基于C++中双向链表和unordered_map的实现。

#include <iostream>
#include <unordered_map>
#include <utility>

using namespace std;

// List nodeclass
class Node
{
    public:
    int data;
    Node* next;
    Node* prev;
};

//Doubly Linked list
class DLList
{
    public:

    DLList()
    {
        head = NULL;
        tail = NULL;
        count = 0;
    }

    ~DLList() {}

    Node* addNode(int val);
    void print();
    void removeTail();
    void moveToHead(Node* node);

    int size()
    {
        return count;
    }

    private:
    Node* head;
    Node* tail;
    int count;
};

// Function to add a node to the list

Node* DLList::addNode(int val)
{
    Node* temp = new Node();

    temp->data = val;
    temp->next = NULL;
    temp->prev = NULL;

    if ( tail == NULL )
    {
        tail = temp;
        head = temp;
    }
    else
    {
        head->prev = temp;
        temp->next = head;
        head = temp;
    }

    count++;

    return temp;
}

void DLList::moveToHead(Node* node)
{
    if (head == node)
        return;

    node->prev->next = node->next;

    if (node->next != NULL)
    {
        node->next->prev = node->prev;
    }
    else
    {
        tail = node->prev;
    }
        node->next = head;
        node->prev = NULL;
        head->prev = node;
        head = node;
}

void DLList::removeTail()
{
    count--;

    if (head == tail)
    {
        delete head;
        head = NULL;
        tail = NULL;
    }
    else
    {
        Node* del = tail;
        tail = del->prev;
        tail->next = NULL;
        delete del;
    }
}

void DLList::print()
{
    Node* temp = head;

    int ctr = 0;

    while ( (temp != NULL) && (ctr++ != 25) )
    {
        cout << temp->data << " ";
        temp = temp->next;
    }
    cout << endl;
}

class LRUCache
{
    public:
        LRUCache(int aCacheSize);
        void fetchPage(int pageNumber);

    private:
        int cacheSize;
        DLList dlist;
        unordered_map< int, Node* > directAccess;
};

    LRUCache::LRUCache(int aCacheSize):cacheSize(aCacheSize) { }

    void LRUCache::fetchPage(int pageNumber)
    {
        unordered_map< int, Node* >::const_iterator it = directAccess.find(pageNumber);

        if (it != directAccess.end())
        {
            dlist.moveToHead( (Node*)it->second);
        }
        else
        {
            if (dlist.size() == cacheSize-1)
               dlist.removeTail();

            Node* node = dlist.addNode(pageNumber);

            directAccess.insert(pair< int, Node* >(pageNumber,node));
        }

        dlist.print();
    }

    int main()
    {
        LRUCache lruCache(10);

        lruCache.fetchPage(5);
        lruCache.fetchPage(7);
        lruCache.fetchPage(15);
        lruCache.fetchPage(34);
        lruCache.fetchPage(23);
        lruCache.fetchPage(21);
        lruCache.fetchPage(7);
        lruCache.fetchPage(32);
        lruCache.fetchPage(34);
        lruCache.fetchPage(35);
        lruCache.fetchPage(15);
        lruCache.fetchPage(37);
        lruCache.fetchPage(17);
        lruCache.fetchPage(28);
        lruCache.fetchPage(16);

        return 0;
}

0
class LRU {
    Map<String, Integer> ageMap = new HashMap<String, Integer>();
    int age = 1;

    void addElementWithAge(String element) {

        ageMap.put(element, age);
        age++;

    }

    String getLeastRecent(Map<String, Integer> ageMap) {
        Integer oldestAge = (Integer) ageMap.values().toArray()[0];
        String element = null;
        for (Entry<String, Integer> entry : ageMap.entrySet()) {
            if (oldestAge >= entry.getValue()) {
                oldestAge = entry.getValue();
                element = entry.getKey();
            }
        }
        return element;
    }

    public static void main(String args[]) {
        LRU obj = new LRU();
        obj.addElementWithAge("M1");
        obj.addElementWithAge("M2");
        obj.addElementWithAge("M3");
        obj.addElementWithAge("M4");
        obj.addElementWithAge("M1");
    }

}

0

扩展Peter Lawrey的答案

removeEldestEntry(java.util.Map.Entry)方法可以被覆盖,以在向映射添加新映射时自动强制执行删除过期映射的策略。

因此,可以覆盖LinkedHashMap的FIFO行为以实现LRU。

public class LRUCache<K, V> extends LinkedHashMap<K, V> {

    private int cacheSize;

    public LRUCache(int cacheSize) {
        super(cacheSize * 4 / 3, 0.75f, true);
        this.cacheSize = cacheSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() >= cacheSize;
    }

    public static void main(String args[]) {
        LRUCache<Integer, Integer> lruCache = new LRUCache<>(5);

        lruCache.put(1, 1);
        lruCache.put(2, 2);
        lruCache.put(3, 3);
        lruCache.put(1, 4);
        lruCache.put(2, 5);
        lruCache.put(7, 6);

        System.out.println(lruCache.keySet());

        lruCache.put(1, 4);
        lruCache.put(2, 5);

        System.out.println(lruCache.keySet());
    }
} 

同样的注释:最老的条目也可能是最近使用的。我们需要删除最不常用的,而不是最老的条目。 - Amrish Pandey
通过设置访问顺序标志,您可以获取访问顺序(即LRU)。LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)。 - bhushan5640
同意。撤回我的评论。 - Amrish Pandey

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