Skip to content

源码:labs/foundations/cache-eviction-demo/src/SimpleLfuCache.java

  • 原始路径:labs/foundations/cache-eviction-demo/src/SimpleLfuCache.java
  • 类型:java
java
import java.util.HashMap;
import java.util.Map;

/**
 * 手写 LFU:按访问次数淘汰;同频率时淘汰最久未用的(频率桶内 LRU)。
 *
 * 理论:docs/00-foundations/04-lru-cache-and-eviction-policies.md
 *
 * 运行:
 * cd labs/foundations/cache-eviction-demo
 * javac -d out src/SimpleLfuCache.java && java -cp out SimpleLfuCache
 */
public class SimpleLfuCache {

    static final int CAPACITY = 3;

    public static void main(String[] args) {
        System.out.println("====== 手写 LFU(频率 + 同频 LRU tie-break)======\n");

        SimpleLfuCache cache = new SimpleLfuCache(CAPACITY);

        cache.put("A", "1");
        cache.put("B", "2");
        cache.put("C", "3");
        cache.printState("put A,B,C(频率均为 1)");

        cache.get("A");
        cache.get("A");
        cache.get("A");
        cache.printState("get A ×3 → A.freq=4");

        cache.get("B");
        cache.printState("get B ×1 → B.freq=2");

        cache.put("D", "4");
        cache.printState("put D(满员:踢 freq 最小的 C,不是 A)");

        System.out.println("\n====== 与 LRU 对比 ======\n");
        System.out.println("LRU:get D 一百次 ≈ get D 一次(只更新「最近」位置)");
        System.out.println("LFU:get A 三次后 A 更难被淘汰(频率更高)");
    }

    static class Node {
        final String key;
        String value;
        int freq;
        Node prev;
        Node next;

        Node(String key, String value) {
            this.key = key;
            this.value = value;
            this.freq = 1;
        }
    }

    static class FreqList {
        final Node head = new Node("", "");
        final Node tail = new Node("", "");

        FreqList() {
            head.next = tail;
            tail.prev = head;
        }

        void addToHead(Node node) {
            linkAfter(head, node);
        }

        boolean isEmpty() {
            return head.next == tail;
        }

        Node removeEldest() {
            Node eldest = tail.prev;
            if (eldest == head) {
                return null;
            }
            unlink(eldest);
            return eldest;
        }
    }

    private final int capacity;
    private final Map<String, Node> keyTable = new HashMap<>();
    private final Map<Integer, FreqList> freqTable = new HashMap<>();
    private int minFreq;

    SimpleLfuCache(int capacity) {
        this.capacity = capacity;
    }

    String get(String key) {
        Node node = keyTable.get(key);
        if (node == null) {
            return null;
        }
        increaseFreq(node);
        return node.value;
    }

    void put(String key, String value) {
        if (capacity <= 0) {
            return;
        }
        Node node = keyTable.get(key);
        if (node != null) {
            node.value = value;
            increaseFreq(node);
            return;
        }
        if (keyTable.size() >= capacity) {
            FreqList list = freqTable.get(minFreq);
            Node evicted = list.removeEldest();
            if (evicted != null) {
                keyTable.remove(evicted.key);
                System.out.println("  >> evict LFU key=" + evicted.key + " freq=" + evicted.freq);
            }
            if (list.isEmpty()) {
                freqTable.remove(minFreq);
            }
        }
        Node created = new Node(key, value);
        keyTable.put(key, created);
        freqBucket(1).addToHead(created);
        minFreq = 1;
    }

    void increaseFreq(Node node) {
        int oldFreq = node.freq;
        FreqList oldList = freqBucket(oldFreq);
        unlink(node);
        if (oldList.isEmpty()) {
            freqTable.remove(oldFreq);
            if (minFreq == oldFreq) {
                minFreq = oldFreq + 1;
            }
        }
        node.freq = oldFreq + 1;
        freqBucket(node.freq).addToHead(node);
    }

    FreqList freqBucket(int freq) {
        return freqTable.computeIfAbsent(freq, f -> new FreqList());
    }

    static void unlink(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    static void linkAfter(Node anchor, Node node) {
        node.prev = anchor;
        node.next = anchor.next;
        anchor.next.prev = node;
        anchor.next = node;
    }

    void printState(String label) {
        System.out.println("--- " + label + " ---");
        System.out.print("  keyTable: ");
        keyTable.forEach((k, n) -> System.out.print(k + "(f=" + n.freq + ") "));
        System.out.println();
        System.out.println("  minFreq=" + minFreq);
        System.out.println();
    }
}