Lru的理解

Lru理解:

LRU这个概念映射到现实生活中非常好理解,就好比说小明的书柜中有很多书,假设他的书都只能放在这个柜子里,小明每过一阵子小明就会买新书,不久小明的书柜就放满了书。这个小明想了个办法,每次看完书后,就将书放回书柜的第一个,然后丢掉书柜最后一本书,也就是最久没有看过那本书。这就是LRU策略。

映射到计算机概念中,上述例子中小明的书柜就是内存,而小明的书就是缓存数据。我们的内存是有限的。所以当缓存数据在内存越来越多,以至于无法存放即将到来的新缓存数据时,就必须扔掉最不常用的缓存数据。所以对于LRU的抽象总结如下:

  • 缓存的容量是有限的
  • 当缓存容量不足以存放需要缓存的新数据时,必须丢掉最不常用的缓存数据

java实现

我们访问缓存通常使用一个字符串来定位缓存数据(事实上其他数据形式也没有关系),这种场景我们反射性的想到HashMap。所以我们先来简单的定义一下LRUCachce类。

import java.util.HashMap;

/**
 * @author ranjun
 * @date 2018/12/14 15:04
 */

/**
 * 具有一定capacity的Lru缓存
 */
public class LruCache {
    private Integer capacity;
    private HashMap<String,Object> map = null;

    public LruCache(Integer capacity){
        this.capacity = capacity;
        map = new HashMap<>();
    }

    public Object get(String key){
        return map.get(key);
    }

    public void set(String key,Object value){
        map.put(key,value);
    }
}

这样我们仅仅是定义了一个容量有限的LRUCache,可以存取数据,但并没有实现缓存容量不足时丢弃最不常用的数据的功能,而这件事做起来似乎显得稍微麻烦一些,问题在于我们如何找到最久没有用的缓存。

一个最容易想到的办法是我们在给这个缓存数据加一个时间戳,每次get缓存时就更新时间戳,这样找到最久没有用的缓存数据问题就能够解决,但与之而来的会有两个新问题:

  • 虽然使用时间戳可以找到最久没用的数据,但我们最少的代价也要将这些缓存数据遍历一遍,除非我们维持一个按照时间戳排好序的SortedList。
  • 添加时间戳的方式为我们的数据带来了麻烦,我们并不太好在缓存数据中添加时间戳的标识,这可能需要引入新的包含时间戳的包装对象。

而且我们的需要只是找到最久没用使用的缓存数据,并不需要精确的时间。添加时间戳的方式显然没有利用这一特性,这就使得这个办法从逻辑上来讲可能不是最好的。

然而办法总是有的,我们可以维护一个链表,当数据每一次查询就将数据放到链表的head,当有新数据添加时也放到head上。这样链表的tail就是最久没用使用的缓存数据,每次容量不足的时候就可以删除tail,并将前一个元素设置为tail,显然这是一个双向链表结构,因此我们定义LruNode如下:


/**
 * @author ranjun
 * @date 2018/12/14 15:18
 */
public class LruNode {
    String key;
    Object value;
    LruNode prev;
    LruNode next;
    public LruNode(String key, Object value) {
        this.key = key;
        this.value = value;
    }
}

而LruCache的简单实现最终如下:

package com.lru;

import java.util.HashMap;

/**
 * @author ranjun
 * @date 2018/12/14 15:04
 */

/**
 * 具有一定capacity的Lru缓存
 */
public class LruCache {
    private Integer capacity;
    private HashMap<String,Object> map = null;
    //缓存头部
    private LruNode head;
    //缓存尾部
    private LruNode tail;

    public LruCache(Integer capacity){
        this.capacity = capacity;
        map = new HashMap<>();
    }

    //查询节点
    public Object get(String key){
        LruNode node = (LruNode) map.get(key);
        if(node != null){
            //将刚刚操作的元素放到head
            remove(node,false);
            setHead(node);
            return node.value;
        }
        return null;
    }

    //添加节点
    public void set(String key,Object value){
        //查询链表中是否已有该元素
        LruNode node = (LruNode) map.get(key);
        if(node != null ){
            node = (LruNode) map.get(key);
            node.value = value;
            //暂时清空该节点前后节点
            remove(node,false);
        }else {
            node = new LruNode(key,value);
            if(map.size() >= capacity){
                //当容量不足时删除最久未使用的元素
                remove(tail,true);
            }
            map.put(key,node);
        }
        //将新添加元素放到表头
        setHead(node);
    }

    //添加节点至链表头
    private void setHead(LruNode node){
        if(head != null){
            node.next = head;
            head.prev = node;
        }
        head = node;
        //若链表为空,链表中就只有一个元素
        if(tail == null){
            tail = node;
        }
    }

    //从链表中删除Node,此时要注意该Node是head或者是tail的情形
    private void remove(LruNode node,boolean flag) {
       //如果节点不是表头
        if (node.prev != null) {
            node.prev.next = node.next;
        }
        //如果是表头
        else {
            head = node.next;
        }

        //如果不是链表尾部
        if (node.next != null) {
            node.next.prev = node.prev;
        }
        //如果是链表尾部
        else {
            tail = node.prev;
        }
        //清空前后节点
        node.next = null;
        node.prev = null;

        //从链表中移除使用最少的元素
        if (flag) {
            map.remove(node.key);
        }
    }

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值