LRU算法 + 手写LRU实现缓存淘汰机制

什么是LRU?

如果你学过操作系统,必然有听过 LRU 算法,他是一种常用的页面置换算法
实现的思想:选择最近最久未使用的数据予以淘汰

LRU算法实现分析

1、底层数据结构的分析
缓存,必须要读 + 写 两个操作 ,如果读的效率要快,底层是采用哈希表结构,写的效率要快,底层就要采用 链表,所以 LRU的底层实现
采用的数据结构就是哈希链表 (哈希表 + 双向链表)

实现LRU算法的两种方式

一、借助Jdk 提供的LinkedHashMap

继承LinkedHashMap,重写里面的淘汰判断removeEldestEntry,

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 借助jdk实现LinkedHashMap实现LRU算法
 */
public class JdkLRUCache extends LinkedHashMap {
    
    
    //定义缓存的大小
    private int cacheSize;
    public JdkLRUCache(int cacheSize){
    
    
        //第一个参数cacheSize 定义LinkedHashMap的初始大小
        //0.75是map的加载因子
        //第三个参数 是选择策略;true,在缓存中元素被调用到会往前移动
        super(cacheSize,0.75F,true);
        this.cacheSize = cacheSize;
    }

    /**
     * 重写removeEldestEntry,实现缓存淘汰判断
     * @param eldest
     * @return
     */
    @Override
    protected boolean removeEldestEntry (Map.Entry eldest) {
    
    
    	//判断是否超过了超过了缓存的大小
        return super.size() > cacheSize;
    }

    

}

写下测试,看下结果

@Test
    public void testJdkLRUCache(){
    
    
        JdkLRUCache jdkLRUCache = new JdkLRUCache(3);

        jdkLRUCache.put(1,"a");
        jdkLRUCache.put(2,"b");
        jdkLRUCache.put(3,"c");
        System.out.println(jdkLRUCache.keySet());

        jdkLRUCache.put(4,"d");
        System.out.println(jdkLRUCache.keySet());

        jdkLRUCache.put(3,"c");
        System.out.println(jdkLRUCache.keySet());
        jdkLRUCache.put(3,"c");
        System.out.println(jdkLRUCache.keySet());
        jdkLRUCache.put(3,"c");
        System.out.println(jdkLRUCache.keySet());
        jdkLRUCache.put(5,"x");
        System.out.println(jdkLRUCache.keySet());

    }

在这里插入图片描述

不借助Jdk,手写LRU

第一个,我们的底层实现的数据结构是哈希表 + 双向链表,就是成员变量是

    //自定义的双向链表
    private DoubleLinkedList doubleLinkedList;
    //map实现精确查找
    private Map< K, Node< K, V > > map;

第二个,我们要定义缓存的大小,所以又需要一个成员来记录

    //定义缓存的大小
    private int cacheSize;

双向链表中,组装的是封装了数据的结点,所以我们需要编写一个结点类Node,因为要用map 要查找 Node ,所以Node 结点中需要记录map中定位他的索引值 K key ,还有封装数据的值 V value ,由于是双向链表,所以需要前后各链接一个Node结点

/**
     * 封装数据的Node结点
     */
    class Node< K, V > {
    
    
        //前一个结点
        private Node pre;
        //后一个结点
        private Node next;
        //封装的数据值
        private V value;

        //map中保存该结点的索引key
        private K key;

        //有参构造
        public Node (K key, V value) {
    
    
            this.key = key;
            this.value = value;
        }

        //无参构造
        public Node () {
    
    
        }
    }

双向链表类

/**
     * 双向链表
     */
    class DoubleLinkedList< K, V > {
    
    
        //头结点
        private Node< K, V > head;
        //尾结点
        private Node< K, V > tail;

        //初始化
        public DoubleLinkedList () {
    
    
            head = new Node();
            tail = new Node();
            //形成闭环
            head.next = tail;
            tail.next = head;
        }

        /**
         * 添加Node结点到队头
         */
        public void addHead (Node< K, V > node) {
    
    
            //获取到原本队头的结点
            Node oldFirstNode = head.next;

            //将node挂到head后面
            head.next = node;
            node.pre = head;

            //将node挂到原本队头结点前
            node.next = oldFirstNode;
            oldFirstNode.pre = node;
        }

        /**
         * 删除掉某个结点
         */
        public void removeNode (Node< K, V > node) {
    
    
            Node pre = node.pre;
            Node next = node.next;
            pre.next = next;
            next.pre = pre;

            node.pre = null;
            node.next = null;
        }

        /**
         * 获得队列尾部的结点
         */
        public Node< K, V > getLastNode () {
    
    
            return tail.pre;
        }

    }

下一步,我们初始化 缓存类

    public LRUCache (Integer cacheSize) {
    
    
        this.cacheSize = cacheSize;
        map = new HashMap<>();
        doubleLinkedList = new DoubleLinkedList();
    }

对外提供get,和put方法

    /**
     * 最新查找
     */
    public V get (Integer key) {
    
    
        Node< K, V > node = map.get(key);
        //将node 移动到最前
        doubleLinkedList.removeNode(node);
        doubleLinkedList.addHead(node);
        return node.value;

    }

    /**
     * 添加结点
     */
    public void put (K key, V value) {
    
    
        //将数据封装到Node结点中
        Node dataNode = new Node(key, value);

        if (map.containsKey(key)) {
    
    
            Node< K, V > getNode = map.get(key);
            //将node 移动到最前
            doubleLinkedList.removeNode(getNode);
            doubleLinkedList.addHead(getNode);
        } else {
    
    
            //判断缓存是否已经满了
            if (map.size() >= cacheSize) {
    
    
                //移除掉队尾的元素
                Node lastNode = doubleLinkedList.getLastNode();
                doubleLinkedList.removeNode(lastNode);
                //删除掉map中的管理
                map.remove(lastNode.key);
            }
            //将dataNode添加到双向队列中
            doubleLinkedList.addHead(dataNode);

            //存放到map中进行管理
            map.put(key, dataNode);

        }
    }

为了方便测试查看底层双向链表,重写toString方法

    /**
     * 重写toString 方便待会测试查看底层双向链表
     * @return
     */
    @Override
    public String toString () {
    
    
        StringBuilder stringBuilder = new StringBuilder();
        Node currentNode = doubleLinkedList.head.next;
        for (int i = 0; i < map.size(); i++) {
    
    

            stringBuilder.append(currentNode.key + "   ");
            currentNode = currentNode.next;
        }
        return stringBuilder.toString();
    }

所以整个代码的实现是这样子的

package com.demo.LRU;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 手写一个LRU算法
 * LRU底层采用的数据结构是双向链表 + map
 */
public class LRUCache< K, V > {
    
    
    //自定义的双向链表
    private DoubleLinkedList doubleLinkedList;
    //map实现精确查找
    private Map< K, Node< K, V > > map;
    //定义缓存的大小
    private int cacheSize;

    /**
     * 封装数据的Node结点
     */
    class Node< K, V > {
    
    
        //前一个结点
        private Node pre;
        //后一个结点
        private Node next;
        //封装的数据值
        private V value;

        //map中保存该结点的索引key
        private K key;

        //有参构造
        public Node (K key, V value) {
    
    
            this.key = key;
            this.value = value;
        }

        //无参构造
        public Node () {
    
    
        }
    }

    /**
     * 双向链表
     */
    class DoubleLinkedList< K, V > {
    
    
        //头结点
        private Node< K, V > head;
        //尾结点
        private Node< K, V > tail;

        //初始化
        public DoubleLinkedList () {
    
    
            head = new Node();
            tail = new Node();
            //形成闭环
            head.next = tail;
            tail.next = head;
        }

        /**
         * 添加Node结点到队头
         */
        public void addHead (Node< K, V > node) {
    
    
            //获取到原本队头的结点
            Node oldFirstNode = head.next;

            //将node挂到head后面
            head.next = node;
            node.pre = head;

            //将node挂到原本队头结点前
            node.next = oldFirstNode;
            oldFirstNode.pre = node;
        }

        /**
         * 删除掉某个结点
         */
        public void removeNode (Node< K, V > node) {
    
    
            Node pre = node.pre;
            Node next = node.next;
            pre.next = next;
            next.pre = pre;

            node.pre = null;
            node.next = null;
        }

        /**
         * 获得队列尾部的结点
         */
        public Node< K, V > getLastNode () {
    
    
            return tail.pre;
        }

    }

    public LRUCache (Integer cacheSize) {
    
    
        this.cacheSize = cacheSize;
        map = new HashMap<>();
        doubleLinkedList = new DoubleLinkedList();
    }

    /**
     * 最新查找
     */
    public V get (Integer key) {
    
    
        Node< K, V > node = map.get(key);
        //将node 移动到最前
        doubleLinkedList.removeNode(node);
        doubleLinkedList.addHead(node);
        return node.value;

    }

    /**
     * 添加结点
     */
    public void put (K key, V value) {
    
    
        //将数据封装到Node结点中
        Node dataNode = new Node(key, value);

        if (map.containsKey(key)) {
    
    
            Node< K, V > getNode = map.get(key);
            //将node 移动到最前
            doubleLinkedList.removeNode(getNode);
            doubleLinkedList.addHead(getNode);
        } else {
    
    
            //判断缓存是否已经满了
            if (map.size() >= cacheSize) {
    
    
                //移除掉队尾的元素
                Node lastNode = doubleLinkedList.getLastNode();
                doubleLinkedList.removeNode(lastNode);
                //删除掉map中的管理
                map.remove(lastNode.key);
            }
            //将dataNode添加到双向队列中
            doubleLinkedList.addHead(dataNode);

            //存放到map中进行管理
            map.put(key, dataNode);

        }
    }
    /**
     * 重写toString 方便待会测试查看底层双向链表
     * @return
     */
    @Override
    public String toString () {
    
    
        StringBuilder stringBuilder = new StringBuilder();
        Node currentNode = doubleLinkedList.head.next;
        for (int i = 0; i < map.size(); i++) {
    
    

            stringBuilder.append(currentNode.key + "   ");
            currentNode = currentNode.next;
        }
        return stringBuilder.toString();
    }

}

编写测试
debug看下底层双向链表的变化

    @Test
    public void testLRUCache(){
    
    
        LRUCache lruCache = new LRUCache(3);

        lruCache.put(1,"a");
        lruCache.put(2,"b");
        lruCache.put(3,"c");

        System.out.println(lruCache.toString());

        lruCache.put(4,"d");
        System.out.println(lruCache.toString());

        lruCache.put(3,"c");
        System.out.println(lruCache.toString());
        lruCache.put(3,"c");
        System.out.println(lruCache.toString());
        lruCache.put(3,"c");
        System.out.println(lruCache.toString());
        lruCache.put(5,"x");
        System.out.println(lruCache.toString());

    }

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45844836/article/details/112601062