一个优雅的LRU缓存 Java 实现

Java 实现 LRU缓存

LRU算法是一种常用的缓存算法,用于移除最近最少使用的数据。LRU缓存的核心思想是:当缓存容量已满,且需要插入新数据时,优先移除最近最少使用的数据。这种算法在许多场景下都非常有用,比如数据库缓存、网页缓存等。它可以帮助我们高效地管理有限的存储空间,同时保证最近访问的数据能够快速获取。

实现思路

为了实现一个高效的LRU缓存,我们需要结合两种数据结构:哈希表和双向链表。

  • 哈希表:用于快速定位缓存中的数据。通过键(key)可以快速找到对应的节点。
  • 双向链表:用于维护数据的访问顺序。最近访问的节点会被移动到链表的头部,而最久未访问的节点则位于链表的尾部。

这种结合方式的优势在于:

  • 哈希表提供了快速的查找能力,时间复杂度为O(1)。
  • 双向链表可以方便地移动节点,同时维护访问顺序。

以下是LRU缓存的完整实现代码:

public class LRUCache {
    
    private int capacity; // 缓存容量
    private Map<Integer, Node> map; // 哈希表,用于快速查找
    private Node head; // 双向链表的头节点
    private Node tail; // 双向链表的尾节点

    /**
     * 构造函数,初始化属性字段。虚拟的头节点head和尾节点tail可以简化链表操作。
     */
    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
    }

    /**
     * 如果键存在于哈希表中,获取对应的节点,并将其移动到链表头部。如果键不存在,返回-1。
     */
    public int get(int key) {
        if(map.containsKey(key)){
            Node node = map.get(key);
            move2First(node);
            return node.val;
        }
        return -1;
    }

    /**
     * 如果键已存在,更新节点的值,并将其移动到链表头部。
     * 如果键不存在,检查缓存是否已满:
     *  如果未满,创建新节点并插入。
     *  如果已满,移除链表尾部的节点(最久未访问的节点),并用新节点替换。
     */
    public void put(int key, int value) {
        if(map.containsKey(key)){
            Node node = map.get(key);
            node.val = value;
            move2First(node);
        }else{
            Node node;
            if(map.size() < capacity) node = new Node(key, value);
            else{
                node = tail.prev;
                map.remove(node.key);
                node.val = value;
                node.key = key;
            }
            map.put(key, node);
            move2First(node);
        }
    }

    /**
     * 将节点移动到链表头部
     */
    public void move2First(Node node){
        if(node == head.next) return;
        if(node.next != null) node.prev.next = node.next;
        if(node.prev != null) node.next.prev = node.prev;
        head.next.prev = node;
        node.next = head.next;
        node.prev = head;
        head.next = node;
    }

    // 测试案例
    public static void main(String[] args) {
        LRUCache cache = new LRUCache(2); // 缓存容量为2

        cache.put(1, 1);
        cache.put(2, 2);
        System.out.println(cache.get(1)); // 返回1
        cache.put(3, 3); // 移除键2
        System.out.println(cache.get(2)); // 返回-1(键2已被移除)
        cache.put(4, 4); // 移除键1
        System.out.println(cache.get(1)); // 返回-1(键1已被移除)
        System.out.println(cache.get(3)); // 返回3
        System.out.println(cache.get(4)); // 返回4
    }

}

// 双向链表节点
class Node{
    Node next;
    Node prev;
    int val;
    int key;
    Node(){ }
    Node(int key, int val){
        this.val = val;
        this.key = key;
    }
}

posted @ 2025-02-22 23:18  zzzggb  阅读(166)  评论(0)    收藏  举报