LRU算法

本文解析了LRU(Least Recently Used)缓存淘汰策略,利用哈希表快速查找和双向链表维护使用顺序,详细介绍了插入、查询和淘汰过程。通过实例演示了如何在Java中实现LRU缓存,适用于数据结构和算法设计的学习者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LRU是least Recently Used 的缩写,最近使用数据是热门数据,下一次大概率会再次使用。而最近少使用的数据,大概率下次也用不到,当缓存容量满的时候,优先淘汰最近少使用的数据。简单来说就是缓存容量满时,丢掉最久不用的数据。

假设容量为3 ,按照顺序插入键值对 [1-1],[2-2], [3-3],此时缓存中就是 :

  1. [3-3]
  2. [2-2]
  3. [1-1]
    当我们再插入[4-4]时缓存中就是 :
  4. [4-4]
  5. [3-3]
  6. [2-2]
    此时[1-1]就被淘汰了,当我们此时在查询[2-2] 缓存中就是 :
  7. [2-2]
  8. [4-4]
  9. [3-3]
    键值对存储方面,我们可以使用「哈希表」来确保插入和查询的复杂度为 。
    另外我们还需要额外维护一个「使用顺序」序列。
    我们期望当「新数据被插入」或「发生键值对查询」时,能够将当前键值对放到序列头部,这样当触发 LRU 淘汰时,只需要从序列尾部进行数据删除即可。
    *为了期望在*O(1)复杂度内调整摸个节点的位置,自然就想到了双向链表
    具体的,我们使用哈希表来存储「键值对」,键值对的键作为哈希表的 Key,而哈希表的 Value 则使用我们自己封装的 Node 类,Node 同时作为双向链表的节点。

插入:检查当前键值对是否已经存在于哈希表:
如果存在,则更新键值对,并将当前键值对所对应的 Node 节点调整到链表头部
如果不存在,则检查哈希表容量是否已经达到容量:
没达到容量:插入哈希表,并将当前键值对所对应的 Node 节点调整到链表头部
已达到容量:先从链表尾部找到待删除元素进行删除,然后再插入哈希表,并将当前键值对所对应的 Node 节点调整到链表头部
查询:如果没在哈希表中找到该 Key,直接返回 ;如果存在该 Key,则将对应的值返回,并将当前键值对所对应的 Node 节点调整到链表头部
一些细节:

为了减少双向链表左右节点的「判空」操作,我们预先建立两个「哨兵」节点 head (头)和 tail(尾)。
在这里插入图片描述

//链表的节点
public static class Node{
        public Node pre;//前一个节点
        public Node next;//后一个节点
        public int key;//查询 的键
        public int value;//插入的值

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
        public Node(){}
    }
public class LRUTest {
    Node head,tail;//建立两个「哨兵」节点 head (头)和 tail(尾)
    int capacity;//缓存容量
    int size;//缓存当前存入的个数
    Map<Integer,Node> map;//定义一个Map key就对应这个value中也就是节点Node的key
    public LRUTest(int capacity){
        this.capacity = capacity;
        newLinkedList();//初始化链表
        size = 0;
        map= new HashMap<>(capacity+2);
    }
	//初始化链表
    private void newLinkedList() {
        head = new Node();//头节点
        tail = new Node();//尾节点

        head.next = tail;//头节点的后一个节点就是尾节点
        tail.pre = head;//尾节点的前一个节点就是头节点
    }
 }

在这里插入图片描述请添加图片描述
这样我们就可以往中间插入节点(数据)了

//get操作先查询是否有key存在,如果存在把当前节点删掉,移动到头节点「哨兵」的后面
public int get(int key){
          Node node = null;
        if (cache.containsKey(key)){
            goToHead(node);
            return node.value;
        }
        return -1;
    }
public void put(int key,int value){
		//查询map中是否有对应key的node节点,如果node不等于空
		//说明有,然后value重新赋值,把当前节点删掉,移动到头节点「哨兵」的后面
        Node node = map.get(key);
        if (node!=null){
            node.value = value;
            goToHead(node);
            return;
        }
        //当容量满了尾节点「哨兵」的前一个节点将他删除
        if (size == capacity){
            Node lastNode = tail.pre;
            deleteNode(lastNode);
            cache.remove(lastNode.key);
            size--;
        }
//如果没有查到那么久新建一个节点放到头节点「哨兵」的后面
        Node newNode = new Node();
        newNode.key = key;
        newNode.value = value;
        addNode(newNode);
        cache.put(key,newNode);
        size++;
    }
     private void goToHead(Node node) {
     //先删掉当前节点
        deleteNode(node);
        //添加到头节点
        addNode(node);
    }
	//添加到头节点
    private void addNode(Node node) {
        head.next.pre = node;
        node.next = head.next;

        node.pre = head;
        head.next = node;
    }
    //删掉当前节点
    private void deleteNode(Node node) {
  
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }
    

deleteNode操作
deleteNode操作
addNode操作
在这里插入图片描述

public class LRUTest {
    Node head,tail;
    int capacity;
    int size;
    Map<Integer,Node > map;
    public LRUTest(int capacity){
        this.capacity = capacity;
        newLinkedList();
        size = 0;
        map= new HashMap<>(capacity+2);
    }

    private void newLinkedList() {
        head = new Node ();
        tail = new Node ();

        head.next = tail;
        tail.pre = head;
    }

    public static class Node{
        public Node pre;
        public Node next;
        public int key;
        public int value;

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

        }
    }
    public int get(int key){
        Node node = null;
        if (cache.containsKey(key)){
            node = cache.get(key);
            goToHead(node);
            return node.value;
        }
        return -1;
    }
    public void put(int key,int value){
        Node node = cache.get(key);
        if (node!=null){
            node.value = value;
            goToHead(node);
            return;
        }
        if (size == capacity){
            Node lastNode = tail.pre;
            deleteNode(lastNode);
            cache.remove(lastNode.key);
            size--;
        }

        Node newNode = new Node ();
        newNode.key = key;
        newNode.value = value;
        addNode(newNode);
        cache.put(key,newNode);
        size++;
    }

    private void goToHead(Node node) {
        deleteNode(node);
        addNode(node);
    }

    private void addNode(Node node) {
        head.next.pre = node;
        node.next = head.next;

        node.pre = head;
        head.next = node;
    }

    private void deleteNode(Node node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    public static void main(String[] args) {
      LRUTest lruTest = new LRUTest(3);//设置容量是3

      lruTest.put(1,8);//此时缓存中是[1-8]
      lruTest.put(2,3);//此时缓存中是[2-3],[1-8]
      System.out.println(lruTest.get(1));//此时缓存中是[1-8],[2-3]输出8
      lruTest.put(3,6);//此时缓存中是[3-6],[1-8],[2-3]
      lruTest.put(4,9);//此时缓存中是[4-9],[3-6],[1-8]
      System.out.println(lruTest.get(2));//此时缓存中是[4-9],[3-6],[1-8]没有key为2的所以返回-1
      //控制台输出的就是8 和-1
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值