LRU(JAVA简洁版)

有基础的可以直接去看最下面👇🏻的算法实现

一、算法介绍

最近最久未使用(Least Recently Used    LRU)算法是⼀种缓存淘汰策略,它是大部分操作系统为最大化页面命中率而广泛采用的一种页面置换算法。该算法的思路是,发生缺页中断时,将最近一段时间内最久未使用的页面置换出去。 从程序运行的原理来看,最近最久未使用算法是比较接近理想的一种页面置换算法,这种算法既充分利用了内存中页面调用的历史信息,又正确反映了程序的局部问题

虚拟页式存储管理,则是将进程所需空间划分为多个页面,内存中只存放当前所需页面,其余页面放入外存的管理方式。
有利就有弊,虚拟页式存储管理减少了进程所需的内存空间,却也带来了运行时间变长这一缺点:进程运行过程中,不可避免地要把在外存中存放的一些信息和内存中已有的进行交换,由于外存的低速,这一步骤所花费的时间不可忽略。因而,采取尽量好的交换算法以减少读取外存的次数,也是相当有意义的事情。

二、基本原理

假设 序列为 4 3 4 2 3 1 4 2
物理块有3个
4调入内存 4
3调入内存 3 4
4调入内存 4 3
2调入内存 2 4 3
3调入内存 3 2 4
1调入内存 1 3 2(因为最少使用的是4,所以丢弃4)
4调入内存 4 1 3(原理同上)
2调入内存 2 4 1

规律就是,如果新存入或者访问一个值,则将这个值放在队列开头。如果存储容量超过上限cap,那么删除队尾元素,再存入新的值。

我们下面通过一个简单的存储int的方式来实现LRU cache,实现put和get功能。

三、数据结构

⾸先要接收⼀个参数作为缓存的最⼤容量, 然后实现两个 API, ⼀个是 put(key, val) ⽅法存⼊键值对,get(key) ⽅法获取 key 对应的 val, 如果 key 不存在则返回null,get 和 put ⽅法必须都是 O(1) 的时间复杂度。

因此LRU cache 的数据结构的必要的条件: 查找快, 插⼊快, 删除快, 有顺序之分。那么, 什么数据结构同时符合上述条件呢? 哈希表查找快, 但是数据⽆固定顺序; 链表有顺序之分, 插⼊删除快, 但是查找慢。 所以结合⼀下, 形成⼀种新的数据结构: 哈希链表。如下图所示:

四、算法细节

新插入的元素或者最新查询的元素要放到链表的头部,对于长时间未访问的元素要放到链表尾部,所以每次插入或者查询都需要维护链表中元素的顺序。

使用哈希表的原因是查询时间复杂度为O(1),使用双向链表的原因是对于删除和插入操作时间复杂度为O(1)。

其中哈希表中存储的 key 为 K,value 为 Node<K,V> 的引用,双向链表存储的元素为Node<K,V>的引用.

put 方法是线程安全方法,定义如下所示:

public synchronized void put(K key,V value)

对于put操作:

①首先判断缓存中 元素 K 是否存在,如果存在,则把链表中的元素Node<K, V>删除,map中的数据<K, Node<K, V> >不用删除,再在链表头部插入元素,并更新map,直接返回即可 ;

②缓存不存在,并且缓存没有满的话,直接把元素插入链表的表头,缓存满了的话移除表尾元素(最旧未访问元素),将元素K插入表头,增加map中的<K, Node<K, V>>, 更新map。

get 方法是线程安全方法,定义如下所示:

public synchronized V get(K key)

对于get操作:

首先要判断 缓存中(map)是否存在,如果存在则把该节点删除并在链表头部插入该元素并更新map 返回当前元素即可,如果map不存在 则直接返回null;

五、算法实现

// LRUCache: 最近最久未使用,哈希表(快速定位) + 双向链表(快速头部插入和快速尾部删除)

// 1. Node类:int key, int value, Node prev, Node next,构造函数
// 2. LRUCache类:
// 2.1 size, capacity, Map<Integer, Node> cache = new HashMap<>(); 虚拟头尾节点Node head, Node tail, 构造函数
// 2.2 get方法  1.缓存未命中,返回-1; 2.缓存命中,删除该节点,头部插入该节点
// 2.3 put方法  1.key不存在:新建节点,添加缓存,新节点移至头部,size++; 1.1 如果超容,则删除链表尾部节点,删除缓存中对应key,更新sieze; 2. key存在,更新value,删除该节点,头部插入该节点

class Node {
    int key;
    int value;
    Node prev;
    Node next;
    public Node() {}
    public Node(int _key, int _value) {key = _key; value = _value;}
}
public class LRUCache {
    private Map<Integer, Node> cache = new HashMap<>();
    private int size;
    private int capacity;
    private Node head, tail;
    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        // 虚拟头部和虚拟尾部节点
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
    }
    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) return -1;
        removeNode(node);
        addToHead(node);
        return node.value;
    }
    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node == null) {  
            Node newNode = new Node(key, value);
            cache.put(key, newNode);
            addToHead(newNode);
            ++size;
            if (size > capacity) {
                Node tailNode = tail.prev;
                removeNode(tailNode);
                cache.remove(tailNode.key);
                --size;
            }
        } else {
            node.value = value;
            removeNode(node);
            addToHead(node);
        }
    }
    // 插入节点(头部)
    private void addToHead(Node node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }
    // 删除节点
    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
}
### 1. JavaLRU Cache 的定义 LRU 缓存表示“最近最少使用的缓存”[^2]。它是一种高效的数据存储策略,用于管理固定大小的内存空间。当缓存达到其最大容量时,会移除最长时间未访问的项目以腾出空间。 --- ### 2. 实现思路 #### 数据结构的选择 为了实现高效的 `get()` 和 `put()` 操作,通常采用 **哈希表** 结合 **双向链表** 的方式来构建 LRU 缓存[^3]。 - 哈希表提供 O(1) 时间复杂度的查找能力。 - 双向链表维护元素的使用顺序,便于快速移动节点到头部或将尾部节点删除。 另一种更简洁的方式是利用 Java 提供的内置类 `LinkedHashMap`,通过重写其 `removeEldestEntry` 方法即可轻松实现 LRU 功能[^4]。 --- ### 3. 使用 `LinkedHashMap` 实现 LRU Cache 以下是基于 `LinkedHashMap` 的简单实现: ```java import java.util.LinkedHashMap; import java.util.Map; public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); // accessOrder=true 表示按照访问顺序排序 this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; // 超过容量则移除最早插入的条目 } } ``` 在这个实现中: - 构造函数设置了初始容量以及加载因子,并启用了按访问顺序排列的功能 (`accessOrder=true`)。 - `removeEldestEntry` 方法会在插入新元素时自动调用,判断当前缓存是否超出指定容量并决定是否移除最旧项。 --- ### 4. 手动实现 LRU Cache 如果不想依赖于 `LinkedHashMap`,可以通过手动组合哈希表和双向链表完成自定义实现。以下是一个完整的例子: ```java class Node<K, V> { K key; V value; Node<K, V> prev; Node<K, V> next; public Node(K key, V value) { this.key = key; this.value = value; } } public class CustomLRUCache<K, V> { private Map<K, Node<K, V>> map; private int capacity; private Node<K, V> head; private Node<K, V> tail; public CustomLRUCache(int capacity) { this.map = new HashMap<>(); this.capacity = capacity; this.head = null; this.tail = null; } public V get(K key) { if (!map.containsKey(key)) return null; Node<K, V> node = map.get(key); moveToHead(node); return node.value; } public void put(K key, V value) { if (capacity <= 0) return; if (map.containsKey(key)) { Node<K, V> node = map.get(key); node.value = value; moveToHead(node); } else { if (map.size() >= capacity) { evictLeastRecentlyUsed(); } Node<K, V> newNode = new Node<>(key, value); addToHead(newNode); map.put(key, newNode); } } private void moveToHead(Node<K, V> node) { if (node == head) return; removeNode(node); addToHead(node); } private void evictLeastRecentlyUsed() { if (tail != null) { map.remove(tail.key); removeNode(tail); } } private void removeNode(Node<K, V> node) { if (node.prev != null) { node.prev.next = node.next; } else { // 如果要移除的是头结点 head = node.next; } if (node.next != null) { node.next.prev = node.prev; } else { // 如果要移除的是尾结点 tail = node.prev; } } private void addToHead(Node<K, V> node) { node.next = head; node.prev = null; if (head != null) { head.prev = node; } head = node; if (tail == null) { tail = node; } } } ``` 此代码实现了基本的 `get()` 和 `put()` 操作,同时保持了时间复杂度为 O(1)。 --- ### 5. 总结 无论是借助 `LinkedHashMap` 还是手动生成双端队列配合哈希表,都可以有效实现 LRU 缓存逻辑。前者更为简便快捷,适合日常开发;后者则提供了更大的灵活性,适用于特定场景下的优化需求。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值