代码编织梦想

简单介绍LRU缓存算法:
LRU(Least recently used)即最近最少使用。将数据添加到缓存中,当缓存满了的时候,移除最近最少访问的数据,留出空间存放新添加的数据。

1. LRU缓存算法的基本结构

  • 缓存大小 + hashmap + 双向链表
  • 操作包含两个:
    • put():添加数据到缓存
    • get():获取缓存中的数据

2. 操作描述

get()操作:

  • 先判断哈希表中是否存在key
  • 如果不存在则直接返回-1
  • 如果存在,则通过hashmap定位到链表中key对应的节点,删除该节点,然后在链表尾部插入一个相同的节点(通过删除+添加 替换移动操作,因为移动时间复杂度更高),返回key对应节点的value值

put()操作:

  • 先判断哈希表中是否存在key
  • 如果存在,则通过hashmap定位到链表中key对应的节点,更新该节点的value为put的新value值,删掉,然后在尾部插入一个相同的节点
  • 如果不存在,则在链表尾部插入新节点(同时往hashmap中添加key),再判断链表长度是否超过缓存容量上限,如果超了,则删掉链表的头部节点(同时删除hashmap中对应的key),如果没超,则不做任何处理

3. 基于HashMap和双向链表的原生实现

import java.util.HashMap;

/**
 * LRU缓存算法实现:基于HashMap+双向链表
 * O(1)复杂度实现get()和put()操作
 */
public class LRUCache{
    private int cap; // 缓存大小
    private HashMap<Integer, Node> map; // hashmap
    public DoubleList cache; // 双向链表


    class Node {
        public int key, val;
        public Node prev, next;
        public Node(int key, int val){
            this.key = key;
            this.val = val;
        }
    }

    class DoubleList {
        private Node head, tail;
        private int size;

        /**
         * 初始化双向链表
         */
        public DoubleList(){
            head = new Node(-1, -1);
            tail = new Node(-1, -1);
            head.next = tail;
            tail.prev = head;
            size = 0;
        }

        /**
         * 在链表尾部添加节点
         * @param node
         */
        public void addLast(Node node){
            tail.prev.next = node;
            node.prev = tail.prev;
            node.next = tail;
            tail.prev = node;
            map.put(node.key, node); // 将该节点的信息放入map中
            size++;
        }

        /**
         * 移除节点
         * @param node
         */
        public void remove(Node node){
            node.prev.next = node.next;
            node.next.prev = node.prev;
            map.remove(node.key); // 从map中移除该节点的信息
            size--;
        }

        /**
         * 移除链表头部节点
         * @return
         */
        public Node removeFirst(){
            if(head.next == tail){
                return null;
            }
            Node first = head.next;
            remove(first);
            return first;
        }

        public int size(){
            return this.size;
        }
    }

    /**
     * 构造函数:初始化缓存大小,HashMap和DoubleList
     * @param cap
     */
    public LRUCache(int cap){
        this.cap = cap;
        map = new HashMap<>();
        cache = new DoubleList();
    }


    /**
     * 添加的时候,先判断原map中有没有该node:
     *  有的话,先移除,再在链表尾部添加该node(保证节点最近刚被访问)
     *  没有的话,在链表尾部添加该node,然后判断有没有超出缓存容量:
     *    有的话,移除头部节点(最久没被访问)
     *    没有的话,不做任何处理
     * @param key
     * @param val
     */
    public void put(int key, int val){
        Node node = new Node(key, val);
        if(map.containsKey(key)){
            cache.remove(node);
            cache.addLast(node);
        }else{
        	cache.addLast(node); // 在尾部添加新的节点
            if(cache.size > this.cap){ // 超出缓存容量大小
                cache.removeFirst(); // 移除头部最久未被访问的节点
            }
        }
    }

    /**
     * 获取的时候,先判断原map中有没有该node:
     *  没有的话直接返回-1,表示不存在
     *  有的话则先删除,再将该node添加到链表尾部(保证其最近刚被访问)
     */
    public Integer get(int key){
        if(!map.containsKey(key)){
            return -1;
        }
        Node node = map.get(key);
        cache.remove(node);
        cache.addLast(node);
        return map.get(key).val;
    }

    public static void main(String[] args) {
        LRUCache cache = new LRUCache(3);
        cache.put(1, 1);
        cache.put(2, 2);
        cache.put(3, 3);
        cache.put(4, 4);
        System.out.println(cache.get(3));
        Node p = cache.cache.head;
        // 依次打印缓存中的node值
        System.out.println("------cache-------");
        while (p!= null) {
            System.out.println(p.val);
            p = p.next;
        }
    }
}

4. 基于LinkedHashMap的实现

import java.util.LinkedHashMap;

/**
 * LRU缓存算法实现:基于LinkedHashMap
 */
public class LRUCache1 {
    private int cap;
    private LinkedHashMap<Integer, Integer> cache;

    public LRUCache1(int cap){
        this.cap = cap;
        cache = new LinkedHashMap<>();
    }

    public Integer get(int key){
        if(!cache.containsKey(key)){
            return -1;
        }else{
            Integer val = cache.get(key);
            cache.remove(key);
            cache.put(key, val);
            return val;
        }
    }

    public void put(int key, int val){
        if(cache.containsKey(key)){
            cache.remove(key);
            cache.put(key, val);
        }else{
        	cache.put(key, val);
            if(cache.size() >= cap){ // 如果超出缓存,则移除最久未被访问的元素(头部)
                Integer first = cache.keySet().iterator().next();
                cache.remove(first);
            }
        }
    }

    public static void main(String[] args) {
        LRUCache1 cache1 = new LRUCache1(4); // 初始化大小为4的缓存
        cache1.put(1, 1);
        cache1.put(2, 2);
        cache1.put(3, 3);
        System.out.println("获取key为1的缓存:" + cache1.get(1)); // 查询key为1的缓存后,元素顺序:2 3 1
        cache1.put(4, 4); // 添加后,元素顺序:2 3 1 4
        cache1.put(5, 5); // 此时缓存容量满了(在尾部添加新元素后,需要删除头部元素),添加后,元素顺序:3 1 4 5
        System.out.println(cache1.cache);
    }
}

5. 参考文档

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_28742063/article/details/130841406

从一道go逆向出发,讨论类tea的逆算法-爱代码爱编程

tea代码很短,经常被直接复制为源码(而不是像标准算法那样调库)。在ctf逆向中也算比较常见,复杂度适中。 例题是一道go逆向,经go parser处理后,核心代码如下图。 panic算是go的专有名词,类似异常。 pan

【1073. 负二进制数相加】-爱代码爱编程

来源:力扣(LeetCode) 描述: 给出基数为 -2 的两个数 arr1 和 arr2,返回两数相加的结果。 数字以 数组形式 给出:数组由若干 0 和 1 组成,按最高有效位到最低有效位的顺序排列。例如,arr

【leetcode刷题】算法:最长公共前缀-爱代码爱编程

文章目录 一、题目描述二、解题思路2.1 解法12.2 解法22.3 解法32.4 解法4 三、结果提交 一、题目描述 二、解题思路 2.1 解法1 class Solution:

最短路径问题-爱代码爱编程

如图,设定源点为D,终点为A,则D到A的最短路径是多少?  算法思路: 第一步,从源点D出发,此时能到达的选择是C和E,我们根据路径长度选择最少的作为下一个节点,于是选择C, 第二步,到达C后,标记C已经走过了,后续再做选择时,排除C。然后将所有C能到达的节点告知D,也就是B、F、E。由D来分辨,B、F、E这些点,是通过C节点路最短,还是D现有方

代码随想录算法训练营第二十九天|491.递增子序列、46.全排列、47.全排列 ii-爱代码爱编程

目录 491.递增子序列 46.全排列 47.全排列 II 491.递增子序列 本题和大家刚做过的 90.子集II 非常像,但又很不一样,很容易掉坑里。  代码随想录 视频讲解:回溯算法精讲,树层去重与树枝去重 | LeetCode:491.递增子序列_哔哩哔哩_bilibili

【数据结构】树,二叉树,满二叉树,完全二叉树的定义和二叉树的基本操作-爱代码爱编程

🎊专栏【数据结构】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 🎆音乐分享【勋章】 大一同学小吉,欢迎并且感谢大家指出我的问题🥰 目录 ⭐树 🏳️‍🌈定义  🏳️‍🌈注意 🍔树的基本术语 ⭐二叉树 🏳️‍🌈定义 🎆二叉树和树的区别 🏳️‍🌈二叉树的性质 ⭐满二叉树 ⭐完全二叉树 🎁遍历二叉树 🎈先序遍历二

路径规划算法:基于绯鲵鲣算法的路径规划算法-爱代码爱编程

路径规划算法:基于绯鲵鲣优化的路径规划算法- 附代码 文章目录 路径规划算法:基于绯鲵鲣优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MA

代码随想录补打卡 347前k个高频元素 一维数组的背包问题-爱代码爱编程

代码如下  func topKFrequent(nums []int, k int) []int {                 ans := []int{}         设置一个结果数组                 mapnum := map[int]int{}   用map记录对应元素出现的次数                 fo

juc学习(一)-爱代码爱编程

目录 多线程并发与并行顺序执行并发执行并行执行 锁机制重量级锁轻量级锁偏向锁锁消除和锁粗化 JMM内存模型Java内存模型重排序volatile关键字