高阶数据结构之LRU Cache

什么是LRU Cache?

        LRU的全称是“Least Recently Used”的缩写,表示最近最少的使用,是一种Cache替换算法;而Cache其实就类似与我们所说的缓存,Cache的容量是有限的,当容量用完之后,如果又有新的内容需要添加进来的时候,就需要挑选并舍弃原有的部分内容,进而腾出空间来存放新的内容。
        总而言之,LRU Cache的替换原则就是将最近最少使用的内容替换掉。

LRU Cache的实现

        实现LRU Cache的方法和思路有很多,但是要保持高效的put和get,比如说要使时间复杂度为O(1)的话,那么只有一种方法(本章所总结的)— 使用双向链表和哈希表实现(最高效、最经典),原因是使用双向链表可以实现任意位置的插入和删除都保持在O(1)的时间复杂度,使用哈希表可以作为缓存,对其进行增删改查的时间复杂度也是保持在O(1)的。

JDK中自带的数据结构

        在自己模拟实现LRU Cache之前,先来简单总结一下JDK中自带的实现LRU Cache的数据结构。

        JDK中,需要使用util包下的LinkedHashMap类。
在这里插入图片描述
        基于插入顺序:这其实就相当于一个单调队列,并没有将最近访问的元素放到双向链表的最后。
        基于访问顺序:一般情况下都是使用这种,原因是这种才是真正意义上的LRU Cache。

LRU Cache的类型题:LRU缓存

class LRUCache extends LinkedHashMap<Integer, Integer>{
    
    
    public int capacity;

    public LRUCache(int capacity) {
    
     
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }
    
    public int get(int key) {
    
    
        return super.getOrDefault(key, -1);
    }
    
    public void put(int key, int value) {
    
    
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
    
    
        return size() > capacity;
    }
}

模拟实现LRU Cache(双向链表+哈希表)

import java.util.HashMap;
import java.util.Map;

public class MyLRUCache {
    
    
    static class DLinkNode{
    
    
        public int key;
        public int value;
        public DLinkNode prev;
        public DLinkNode next;

        public DLinkNode(){
    
    

        }

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

    public DLinkNode head;  //双向链表的头结点
    public DLinkNode tail;  //双向链表的尾结点
    public int usedSize;  //当前链表中的有效节点个数
    public Map<Integer, DLinkNode> map;
    public int capacity;  //容量

    public MyLRUCache(int capacity){
    
    
        this.head = new DLinkNode();
        this.tail = new DLinkNode();
        this.map = new HashMap<>();
        this.capacity = capacity;
        head.next = tail;
        tail.prev = head;
    }

    //存储元素
    public void put(int key, int val){
    
    
        //查找key是否被存储过
        DLinkNode node = map.get(key);
        if(node == null){
    
    
            DLinkNode dLinkNode = new DLinkNode(key, val);
            map.put(key, dLinkNode);
            addToTail(dLinkNode);
            usedSize++;
            if(usedSize > capacity){
    
    
                map.remove(head.next.key);
                removeNode(head.next);
                usedSize--;
            }
        }else{
    
    
            node.value = val;
            moveToTail(node);
        }
    }

    //将当前节点移到尾巴节点
    private void moveToTail(DLinkNode node) {
    
    
        removeNode(node);
        addToTail(node);
    }

    //删除指定节点
    private void removeNode(DLinkNode node) {
    
    
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    //添加节点到链表的尾部
    private void addToTail(DLinkNode node) {
    
    
        tail.prev.next = node;
        node.prev = tail.prev;
        node.next = tail;
        tail.prev = node;
    }

    //访问元素
    public int get(int key){
    
    
        DLinkNode node = map.get(key);
        if(node == null){
    
    
            return -1;
        }else{
    
    
            moveToTail(node);
            return node.value;
        }
    }
}

        以上代码仅供参考。

猜你喜欢

转载自blog.csdn.net/Faith_cxz/article/details/129122420