LRU缓存算法:从原理到Java实现详解

引言

LRU(Least Recently Used,最近最少使用)缓存淘汰算法是一种广泛应用于计算机系统中的缓存管理策略。它的核心思想是:当缓存空间不足时,优先淘汰最长时间未被访问的数据。这种策略基于"局部性原理",即最近被访问过的数据很可能在不久的将来再次被访问。

LRU算法因其高效性和实用性,被广泛应用于数据库缓存、操作系统页面置换、Web服务器缓存等多个领域。理解LRU的实现原理和代码细节,对于提升系统设计能力和解决实际问题具有重要意义。

LFU与LRU的区别

  1. 淘汰策略不同

    • LRU淘汰最近最少使用的项目

    • LFU淘汰使用频率最低的项目

  2. 适用场景不同

    • LRU适合时间局部性强的访问模式

    • LFU适合有明显热点数据的访问模式

  3. 实现复杂度

    • LRU实现相对简单

    • LFU需要维护频率信息,实现更复杂

LRU缓存核心特性

  1. 快速访问:能够在O(1)时间复杂度内完成数据的查找

  2. 有序性:维护数据项的访问顺序

  3. 容量限制:当缓存满时自动淘汰最久未使用的数据

  4. 动态更新:每次访问都会更新数据项的位置

package com.zsy.suanfa;

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

public class LRUCache {
//虚拟头节点(head)和尾节点(tail)简化边界条件处理
    static class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;

        public DLinkedNode() {}

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

    public static class LRUCache2 {
        private final Map cache;
        private final DLinkedNode head;
        private final DLinkedNode tail;
        private final int capacity;
        private int size;

        public LRUCache2(int capacity) {
            this.cache = new HashMap<>();
            this.capacity = capacity;
            this.size = 0;
            head = new DLinkedNode();
            tail = new DLinkedNode();
            head.next = tail;
            tail.prev = head;
        }

        // 将节点移动到链表头部(表示最近使用)
        private void moveToHead(DLinkedNode node) {
            removeNode(node);
            addToHead(node);
        }
        // 从链表中移除节点
        private void removeNode(DLinkedNode node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }
        // 将节点添加到链表头部
        private void addToHead(DLinkedNode node) {
            node.next = head.next;
            node.prev = head;
            head.next.prev = node;
            head.next = node;
        }
        // 移除链表尾部节点(最久未使用)
        private DLinkedNode removeTail() {
            DLinkedNode node = tail.prev;
            removeNode(node);
            return node;
        }
        // 获取键对应的值
        public int get(int key) {
            DLinkedNode node = cache.get(key);
            if (node == null) {
                // 键不存在
                return -1;
            }
            // 更新为最近使用
            moveToHead(node);
            return node.value;
        }
        // 插入或更新键值对
        public void put(int key, int value) {
            DLinkedNode node = cache.get(key);
            if (node == null) {
                // 创建新节点
                DLinkedNode newNode = new DLinkedNode(key, value);
                cache.put(key, newNode);
                addToHead(newNode);
                size++;
                 // 如果超出容量,移除最久未使用的节点
                if (size > capacity) {
                    DLinkedNode removed = removeTail();
                    cache.remove(removed.key);
                    size--;
                }
            } else {
                // 更新现有节点的值并移到头部
                node.value = value;
                moveToHead(node);
            }
        }
    }
}
操作 时间复杂度 说明
get() O(1) 哈希表查找 + 链表节点移动
put() O(1) 哈希表操作 + 链表节点插入/移动
节点移动 O(1) 双向链表节点操作
public class Main {
    public static void main(String[] args) {
        LRUCache2 cache = new LRUCache2(2);
        
        cache.put(1, 1); // 缓存是 {1=1}
        cache.put(2, 2); // 缓存是 {1=1, 2=2}
        System.out.println(cache.get(1));    // 返回 1
        cache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
        System.out.println(cache.get(2));    // 返回 -1 (未找到)
        cache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
        System.out.println(cache.get(1));    // 返回 -1 (未找到)
        System.out.println(cache.get(3));    // 返回 3
        System.out.println(cache.get(4));    // 返回 4
    }
}

你可能感兴趣的:(缓存,java,MAP,链表)