深度剖析LRU

序言

今天我们一起来剖析一下LRU算法,在Redis中当内存满了后,有一种内存淘汰策略就是LRU即淘汰掉最近最少使用的数据。

我们不妨先思考一下,什么样的数据结构可以满足上述条件,对我们可以使用队列,最近使用的数据放在队尾,淘汰数据时淘汰掉队头的数据即可,我们可以使用一个双向链表来表示成一个队列。

LRU实现方法一

很巧妙的是JAVA中有一个类可以完美契合LRU算法,那就是LinkedHashMap,我们可以使用LinkedHashMap来实现LRU。

代码如下:

class LRUCache<K,V> extends LinkedHashMap<K,V>{
    private final int CACHESIZE;
    public LRUCache(int size){
        super(size,0.75f,true);
        CACHESIZE = size;
    }

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

自定义一个类继承LinkedHashMap,在构造函数中传入最大缓存值(即内存中允许存放的最多元素个数)。

我们可以看到在构造函数中调用了父类的构造方法,第一个参数就是最大缓存值,第二个参数大家应该都很常见就是负载因子(扩容时使用的),第三个参数的话有两个选择分别为true和false。

  • 当传入的是true时,LinkedHashMap中存放元素的顺序是按照最近访问的顺序存放的(完美契合LRU)
  • 当传入的是false时,LinkedHashMap中存放元素的顺序是按照元素添加的顺序存放到

removeEldestEntry方法:这个方法就是淘汰元素的方法了,淘汰的条件就是存放元素的个数大于我们指定的最大元素个数后进行淘汰

LRU实现方法二

上面实现的方法就是调用了API实现的,现在我们自己实现一下LRU。

在实现之前先确定几个关键点,存放数据的队列,用双向链表实现,但是双向链表查找数据的效率是非常低的,因此我们可以引入HashMap来帮我们多存放一份数据,这样在查找起来效率就会变高了。

class Node<K,V>{
    K k;
    V v;
    Node next;
    Node prev;
    public Node(){
        next=prev=null;
    }
    public Node(K k,V v){
        this.k = k;
        this.v = v;
        next=prev=null;
    }
}
class DoubleList{
    Node head;
    Node tail;

    public DoubleList(){
        head=new Node();
        tail=new Node();
        head.next=tail;
        tail.prev=head;
    }

    //将数据添加到队尾
    public void addLast(Node node){
        node.next=tail;
        node.prev=tail.prev;
        tail.prev.next=node;
        tail.prev=node;
    }
    //删除队列中的数据
    public void removeNode(Node node){
        node.prev.next=node.next;
        node.next.prev=node.prev;
    }
}
class LRUCache {
    private DoubleList doubleList;
    private Map<Integer,Node<Integer,Integer>> map;
    private final int CACHESIZE;
    public LRUCache(int size){
        CACHESIZE=size;
        doubleList=new DoubleList();
        map=new HashMap<>();
    }
    public void put(int key,int value){
        Node<Integer, Integer> node = map.get(key);
        if (node==null){
            //添加数据,添加到队尾
            Node<Integer,Integer> node1=new Node<>(key,value);
            map.put(key,node1);
            doubleList.addLast(node1);
        }else {
            //之前有数据,说明需要移动该数据在队列中的位置
            //先删除掉旧数据
            doubleList.removeNode(node);
            node.v=value;
            //将数据添加到队头
            doubleList.addLast(node);
        }
        //判断存放元素大小是否超出阈值
        if (map.size()>CACHESIZE){
            //超过阈值,淘汰掉队头数据即最近最少使用的数据
            Node head = doubleList.head.next;
            doubleList.removeNode(head);
            //同时将map中对应数据清除掉
            map.remove(head.k);
        }
    }
    public int get(int key){
        Node<Integer, Integer> node = map.get(key);
        if (node==null){
            throw new RuntimeException("Key不存在");
        }
        //使用过该数据则需要将数据移动到队尾
        doubleList.removeNode(node);
        doubleList.addLast(node);
        return node.v;
    }
    public Set<Integer> keySet(){
        return map.keySet();
    }
}

以上就是LRU的实现啦,其中关键点就是每次使用数据过后,将数据移动到队尾,淘汰数据时淘汰队头数据就可以啦。

我们来看一下运行结果:

 public static void main(String[] args) {
        LRUCache lru=new LRUCache(2);
        lru.put(1,3);
        lru.put(2,4);
        lru.get(1);
        lru.put(3,5);
        System.out.println(lru.keySet());
    }

可以看到,最终淘汰的是key为2的数据,也就是淘汰最近最少使用的数据啦

总结

至此可以看出LRU算法还是非常巧妙的一种算法,理解了它的原理后手写出来其实并不难,希望大家都可以掌握~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guojunjiang12345

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值