淘汰策略之LRU算法

46 阅读1分钟

1. LRU 原理简述

LRU(Least Recently Used)缓存的核心思想是:每次访问缓存时,都会将被访问的元素标记为“最新使用”;当缓存空间满时,淘汰最久未被访问的元素

实现 LRU 通常用双向链表 + 哈希表

  • 哈希表用于 O(1) 查找元素。
  • 双向链表用于维护访问顺序,头部是最近访问,尾部是最久未访问。

2. Java LRU Demo

下面是一个简单的 Java LRU 缓存实现(不依赖第三方库):

import java.util.*;

class LRUCache<K, V> {
    private final int capacity;
    private final Map<K, Node<K, V>> map;
    private final DoubleLinkedList<K, V> list;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new HashMap<>();
        this.list = new DoubleLinkedList<>();
    }

    public V get(K key) {
        if (!map.containsKey(key)) return null;
        Node<K, V> node = map.get(key);
        list.moveToHead(node);
        return node.value;
    }

    public void put(K key, V value) {
        if (map.containsKey(key)) {
            Node<K, V> node = map.get(key);
            node.value = value;
            list.moveToHead(node);
        } else {
            if (map.size() >= capacity) {
                Node<K, V> tail = list.removeTail();
                if (tail != null) map.remove(tail.key);
            }
            Node<K, V> newNode = new Node<>(key, value);
            list.addToHead(newNode);
            map.put(key, newNode);
        }
    }

    // 双向链表节点
    static class Node<K, V> {
        K key;
        V value;
        Node<K, V> prev, next;
        Node(K key, V value) { this.key = key; this.value = value; }
    }

    // 双向链表
    static class DoubleLinkedList<K, V> {
        Node<K, V> head, tail;

        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;
        }

        void moveToHead(Node<K, V> node) {
            if (node == head) return;
            // remove node
            if (node.prev != null) node.prev.next = node.next;
            if (node.next != null) node.next.prev = node.prev;
            if (node == tail) tail = node.prev;
            // add to head
            node.prev = null;
            node.next = head;
            if (head != null) head.prev = node;
            head = node;
        }

        Node<K, V> removeTail() {
            if (tail == null) return null;
            Node<K, V> node = tail;
            if (tail.prev != null) tail.prev.next = null;
            tail = tail.prev;
            if (tail == null) head = null;
            return node;
        }
    }
}

3. 使用示例

public class Main {
    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(2);
        cache.put(1, "A");
        cache.put(2, "B");
        System.out.println(cache.get(1)); // 输出 A
        cache.put(3, "C"); // 淘汰 key=2
        System.out.println(cache.get(2)); // 输出 null
        cache.put(4, "D"); // 淘汰 key=1
        System.out.println(cache.get(1)); // 输出 null
        System.out.println(cache.get(3)); // 输出 C
        System.out.println(cache.get(4)); // 输出 D
    }
}

4. 原理总结

  1. 每次访问元素(get/put),把元素移动到链表头部,表示最近被访问。
  2. 缓存满时,移除链表尾部元素(最久未被访问)。
  3. 哈希表保证查找和插入都是 O(1) 时间复杂度。

这种设计使得 LRU 缓存高效且易于扩展。