LRU 缓存

76 阅读2分钟

LRU 缓存

LRU 缓存算法是 Least Recently Used 的缩写,即最近最少使用。广泛应用于需要高效管理有限缓存空间并优化数据访问速度的场景。

主要核心逻辑,确定有限空间,插入元素在队列前部,插入时不能超过最大空间。

package lru;

import java.util.HashMap;

public class LRUCache<K, V> {

    // Node 双向链表
    private class Node {
        // 键
        private K key;
        // 值
        private V value;
        // 前置节点
        private Node prev;
        // 后置节点
        private Node next;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public Node(){}
    }

    // 最大缓存数量
    private final Integer maxSize;

    // 头节点
    private final Node head;

    // 尾节点
    private final Node tail;

    // Hash
    private final HashMap<K, Node> map;

    // 构造方法
    public LRUCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("Cache size must be positive");
        }
        this.maxSize = maxSize;
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
        map = new HashMap<K, Node>();
    }

    // 获取键值
    public V get(K key) {
        Node node = map.get(key);
        if (node == null) {
            return null;
        }
        // 移动到头节点
        nodeToHead(node);
        return node.value;
    }

    // 添加元素
    public void put(K key, V value) {
        Node node = map.get(key);
        if (node != null) {
            node.value = value;
            nodeToHead(node);
        } else {
            node = new Node(key, value);
            if (map.size() >= maxSize) {
                map.remove(tail.prev.key);
                removeNode(tail.prev);
            }
            map.put(key, node);
            addHeadNode(node);
        }
    }

    // 节点前置
    private void nodeToHead(Node node) {
        // 删除这个节点
        removeNode(node);
        // 加入到头节点
        addHeadNode(node);
    }

    // 添加头节点
    private void addHeadNode(Node node) {
        node.next = head.next;
        node.prev = head;
        head.next.prev = node;
        head.next = node;
    }

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

    // 打印
    private void print() {
        Node item = head.next;
        while (item.next != null) {
            System.out.println(item.key + " : " + item.value.toString());
            item = item.next;
        }
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "aaa");
        cache.put(2, "bbb");
        cache.put(3, "ccc");
        System.out.println(cache.get(1));
        System.out.println(cache.get(2));
        cache.put(4, "ddd");
        cache.print();
    }
}

在Java中,可以使用LinkedHashMap来实现LRU缓存。LinkedHashMap是一个哈希表和链表的数据结构。

package lru;

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUMap<K, V> extends LinkedHashMap<K,V> {

    // 防止被修改
    private final int maxSize;

    // 构造方法:初始化 maxSize 和 LinkedHashMap 参数
    public LRUMap(int maxSize) {
        super(maxSize, 0.75f, true);
        this.maxSize = maxSize;
    }

    // 重写移除最老条目条件
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxSize;
    }

    public static void main(String[] args) {
        LRUMap<Integer,String> cache = new LRUMap<Integer,String>(3);
        cache.put(1, "aaa");
        cache.put(2, "bbb");
        cache.put(3, "ccc");
        System.out.println(cache.get(1));
        System.out.println(cache.get(2));
        cache.put(4, "ddd");
        System.out.println("cache = " + cache);
    }
}

LinkedHashMap 部分源码:

    // 构造方法
    // initialCapacity 初始容量,指定哈希表(Entry[] table)的初始桶数量
    // loadFactor 负载因子,主要作用 决定哈希表何时触发扩容
    // accessOrder 控制键值对的迭代顺序
    //             true 表示按照访问顺序迭代键值对
    //             false:插入顺序。条目按插入顺序排列,不受访问操作影响
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

    // removeEldestEntry 方法是 LinkedHashMap 提供的一个扩展点,允许开发者通过覆盖该方法实现 自定义淘汰策略(尤其是实现 LRU 缓存)。
    // 返回 true,则删除最老的条目(具体“最老”含义由 accessOrder 决定)
    // 默认返回 false(不删除任何条目,保持标准 Map 行为)
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

LRU是平衡实现复杂度与效率的通用缓存策略,核心价值在于利用时间局部性,适用于数据访问模式具有“近期重复性”的场景。