算法通关村第五关 | LRU的设计与实现

1.LRU的含义

 关于LRU,简单来说就是当内存空间满了。不得不淘汰某些数据时,通常是容量满了,选择最久未被使用的数据进行淘汰。

LRU(Least Recently Used)是一种缓存淘汰策略,它基于这样的假设:在一段时间内,最近被访问的数据更有可能在将来被访问到,而最久未被访问的数据更有可能在将来不再被使用。

LRU算法的核心思想是,当缓存空间已满时,优先淘汰最久未被访问的数据。具体实现方式通常是使用双向链表(Doubly Linked List)和哈希表(Hash Map)的结合。

LRU算法的主要步骤如下:

  1. 初始化一个容量固定的缓存,用双向链表和哈希表保存数据。
  2. 当数据被访问时,从哈希表中查找该数据是否存在。
    • 如果数据存在,从双向链表中将该节点移动到链表头部,表示最近使用过。
    • 如果数据不存在,将数据添加到双向链表的头部,并在哈希表中创建一个对应的键值对。
  3. 当缓存已满时,要淘汰最久未被使用的数据。
    • 在双向链表的尾部找到最久未使用的数据节点,并从双向链表和哈希表中移除该节点。

通过使用LRU算法,可以保证缓存中始终存储最近使用的数据,从而提高缓存的命中率和系统性能。在实际应用中,LRU算法广泛应用于数据库查询缓存、操作系统页面置换等场景。

如按照 7 0 1 2 0 3 0 4 的次序访问

2.hash + 双链表实现LRU

        1. Hash的作用用来做到O(1)访问元素,哈希表就是普通的哈希映射,通过缓存数据的键映射到其在双链表的位置。Hash里的数据值key-value结构。value就是我们自己封装的node,key则是键值,也就是Hash的地址。

        2. 双向链表用来实现根据访问情况对元素进行排序,双向链表按照被使用的顺序存储这些键值,靠近头的键值是最近使用的,而靠近尾部的键值对是最久未被使用的。

对于LRU的实现,我们重点关注的是get和put操作

对于get操作,首先判断key是否存在

        不存在,返回-1;

        存在,则key对应的结点是最近被使用过的结点,通过哈希表定位该结点在双向链表的位置。并将其移动到双向链表的头部,最后返回该节点的值,

对于put操作,首先判断key是否存在

        如果不存在,使用key和value创建一个新结点,在双向链表的头部添加该节点,并将key和该节点的值添加进哈希表中,然后判断双向链表是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希中的对应项;

        如果存在,则和get类似,先通过hash定位,更新value的值,并将该节点移到头部,

在双向链表的实现我们用虚拟头和虚拟尾结点辅助操作,先看几个图示例子,

容量为3,首先缓存1,如图a,之后缓存2和3,结构如图b。

之后4进入,则会删除末尾的1,然后4在头部,如果再次访问2,则2的位置会更新, 

 LRU算法在很多地方都有使用,基本计算机的各个地方都有涉及,所以要大概清楚其实现原理,代码实现如下:

public class LRUCache {
    //定义双链表
    class DlinkedNode{
        int key;//哈希键值,也就是哈希地址
        int value;//我们为您自己封装的元素
        DlinkedNode prev;//指向前面元素
        DlinkedNode next;//指向后面元素

        public DlinkedNode() {
        }

        public DlinkedNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    //创建HashMap存储元素
    private Map<Integer,DlinkedNode> cache = new HashMap<Integer,DlinkedNode>();
    //链表存储元素个数
    int size;
    //链表容量
    int capacity;
    //虚拟头、虚拟尾
    private DlinkedNode head,tail;

    //定义链表容量,创建虚拟头尾指针,初始化构造方法
    public LRUCache(int capacity){
        this.size = 0;
        this.capacity = capacity;
        //头、尾
        head = new DlinkedNode();
        tail = new DlinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    //关键字存在,返回值,重新刷新缓存,否则返回-1,
    public int get(int key){
        DlinkedNode node = cache.get(key);
        if (node == null){
            return -1;
        }
        //如果key存在,先通过哈希表定位,再移到头部
        moveToHead(node);
        return node.value;
    }



    //加入值,存在则通过哈希表定位,修改value,并移到头部,不存在,创建新节点,放到头部,判断容量是否超出,超出删除尾节点
    public void put(int key,int value){
        DlinkedNode node = cache.get(key);//不存在会返回null
        if (node == null){
            //key不存在,创建新节点,
            DlinkedNode newnode = new DlinkedNode(key,value);
            //添加进哈希表
            cache.put(key,newnode);
            //添加至双链表头部
            addToHead(newnode);
            ++size;
            if (size > capacity){
                //超出容量,删除双链表尾结点
                DlinkedNode tail = removeTail();
                //删除哈希表中对应项
                cache.remove(tail.key);
                --size;
            }
        }else {
            //key存在,先通过哈希定位,再修改value,再移到头部
            node.value = value;
            moveToHead(node);
        }
    }

    //删除尾部结点
    private DlinkedNode removeTail() {
        DlinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }

    //增加头部结点
    private void addToHead(DlinkedNode node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    //删除结点
    public void removeNode(DlinkedNode node){
        node.next.prev = node.prev;
        node.prev.next = node.next;
    }
    //删除已存在的,增加新结点
    private void moveToHead(DlinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值