【算法】LRU缓存算法的实现

7 篇文章 1 订阅

最近准备面试的时候,发现很多面试官都喜欢问:如何实现 LRU/LFU 缓存机制?于是,对这部分内容简单总结、记录。

什么是 LRU 缓存机制?

LRU 算法,即 least recently used 最近最少使用,是一种用于缓存的数据淘汰策略。当内存不足以容纳新的数据时,需要淘汰最近最少使用的数据。

比如 Redis 的 8 种内存淘汰机制中的 volatile-lru、allkeys-lru 就是基于 LRU 算法的实现

具体的流程如下:

若 LRU 的最大容量为2,访问情况如下:

set(1,1) set(2,2) get(1) set(3,3) set(4,4) get(3)

图示:

LRU

可以对比下文 LFU 缓存中的数据结果【LFU的原理和实现

实现思路

由于缓存的数据都是键值对的形式,我们可以用哈希表存储键值间的关系。但是,简单的哈希表无法保存数据之间的“新旧程度”,所以,我们需要一个数据结构维护键值对数据之间的先后关系,并且在每次 get、set 之后都要调整数据的先后关系。

所以,我们可以使用链表保存数据之前的先后次序,当发生 get、set 之后都将当前数据移动到链表的头节点位置。若内存不足时,只需要每次淘汰的链表的末尾节点就可以保证留在缓存中的数据都是热点数据。

综上分析,我们使用哈希表保存键值之间的关系,使用双向链表(更便于结点移动)保存数据之间的先后顺序。

public class LRU {
    // 最大的容量
    private int size;
    private Map<Integer, Node> map;
    // 头尾节点指针
    private Node head, tail;

    // 使用内部类Node保存键值信息,便于访问
    private static class Node{
        int key;
        int value;
        Node next;
        Node prv;
        public Node(int key,int value){
            this.key = key;
            this.value = value;
        }
    }
    
    public LRU(int capacity) {
        map = new HashMap();
        this.size = capacity;
        head = new Node(0,0);
        tail = new Node(0,0);
        head.next = tail;
        tail.prv = head;
    }

    public int get(int key) {
        if(map.containsKey(key)){
            // 先从原位置删除,在将该节点移动首部
            Node node = map.get(key);
            removeNode(node);
            insertToHead(node);
            return node.value;
        }
        return -1;
    }

    public void set(int key, int value) {
        if (map.containsKey(key)) {
            // 更新值并移动到首部
            Node node = map.get(key);
            node.value = value;
            removeNode(node);
            insertToHead(node);
        }else {
            // 新插入
            Node node = new Node(key, value);
            insertToHead(node);
            map.put(key, node);
        }

        // 超过容量时删除最后的节点
        if (map.size() > size) {
            map.remove(tail.prv.key);
            removeNode(tail.prv);
        }
    }

    // 将该节点从链表原位置中删除
    private void removeNode(Node node){
        node.prv.next = node.next;
        node.next.prv = node.prv;
    }

    // 将节点插入到首位置
    private void insertToHead(Node node){
        node.next = head.next;
        node.prv = head;

        head.next.prv = node;
        head.next = node;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值