手撸高频面试算法LRU缓存,力扣热题


一、什么是LRU?

LRU是一种常用的缓存淘汰策略,全称为“Least Recently Used”,即最近最少使用的意思。这种算法主要用于内存管理、浏览器缓存、数据库系统以及其他需要缓存数据的场景中,用来决定当缓存空间不足时应该移除哪些数据项。

LRU的基本思想是:当缓存满时,优先删除最近最少访问的数据项。这里的“最近最少访问”是指在所有缓存中的数据项里,选择那个自上次被访问以来时间最长的数据项进行淘汰。

二、Java的两种实现

在实现上,LRU通常使用双向链表和哈希表结合的方式。每当有一个新的元素加入缓存时,就在链表尾部添加该元素;而当某个元素被访问时,就将这个元素移动到链表的头部。这样,链表的尾部始终是最久未被访问的元素,当需要腾出空间时,就可以直接移除尾部的元素。

1.双向链表和哈希表结合实现

代码如下:

class LRUCache {
    class Node{ // 定义内部链表节点类
        int key;
        int value;
        Node prev;  //前驱
        Node next;  //后继
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    // 定义缓存容量、大小
    private int capacity;
    private int size;
    // 定义头和尾节点,便于链表的操作
    private Node head;
    private Node tail;
    private Map<Integer, Node> cache = new HashMap<>(); // 定义map用于快速查找结果


    public LRUCache(int capacity) { // 初始化
        this.capacity = capacity;
        this.size = 0;
        this.head = new Node(0, 0);
        this.tail = new Node(0, 0);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 移动到链表头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node == null) { // 新增
            node = new Node(key, value);
            cache.put(key, node);
            addNode(node);
            size++;
            if (size > capacity) { // 超出容量时移除尾节点
                Node tail = removeTail();
                cache.remove(tail.key);
                size--;
            }
        } else { // 更新并移动到头节点
            node.value = value;
            moveToHead(node);
        }
    }

    private void moveToHead(Node node) {
        removeNode(node);
        addNode(node);
    }

    // 移除节点
    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    // 头插
    private void addNode(Node node) {
        node.next = head.next;
        node.prev = head;
        head.next.prev = node;
        head.next = node;
    }
    
    // 移除尾节点
    private Node removeTail() {
        Node node = tail.prev;
        removeNode(node);
        return node;
    }

实现说明:

  • 用一个双向链表结构和一个hash表结构配合实现,双向链表用于保证数据顺序,hash表用于快速检索数据
  • 在查询时需要将数据移动到表头
  • 插入时进行链表头插,若数据size大于capacity,则需要移除尾节点
  • 更新数据时,将节点移动到头部

2.LinkedHashMap快速实现LRU缓存

代码如下:

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

    public LRUCache(int capacity) {
        super(capacity, 0.75F, true); // 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缓存?实际上看过LinkedHashMap源码的小伙伴可能会有所了解。
LinkedHashMap 实现 LRU 的关键在于它的构造函数参数:accessOrder 和 removeEldestEntry 方法。

  • accessOrder: 当设置为 true 时,LinkedHashMap 将按照访问顺序而不是插入顺序排序条目。这意味着每次访问一个条目后,它都会被移到链表的末尾,表示它是最新访问的。
  • removeEldestEntry: 这是一个可选的方法引用,当 LinkedHashMap 大小超过设定限制时调用此方法。如果返回 true,那么最老的条目(根据 accessOrder 决定的顺序)会被移除。
  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java码农杂谈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值