缓存算法:LFU和LRU的JAVA实现

LFU、LRU的实现总结

LFU和LRU均是常见的缓存算法,其中LFU表示最不经常使用,LRU表示最近最少使用。

10.1 LRU(Least Recently Used)

定义

LRU (最近最少使用) 缓存机制要求支持以下操作: 获取数据 get 和 写入数据 put 。

  • 获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
  • 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
  • 在 O(1) 时间复杂度内完成这两种操作?
示例
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4
分析
  • 为了在O(1)时间完成get(key)操作,很容易想到使用HashMap。
  • 但这个算法要求当缓存达到容量时,可以删去最近最少使用的项,那么对于每一组(K,V)对象,考虑把他们放入一个双向链表中,每对一个(K,V)对操作时,都应该将其放入队列头,而删除时直接删去队列尾。
代码实现
public class LRU {

	private int capacity;
    private HashMap<Integer,Node> cache;
    private Node head;
    private Node tail;



    public LRU(int capacity) {
        this.capacity=capacity;
        cache = new HashMap<>(capacity);
    }

    public int get(int key) {
        if(capacity==0){
            return -1;
        }
        Node node = cache.get(key);
        if(node==null){
            return -1;
        }
        recentUse(node);
        return node.getValue();
    }

    private void recentUse(Node node) {
        Node pre = node.getPre();
        Node next = node.getNext();
        if(pre==null){
            return;
        }
        if(next!=null){
            next.setPre(pre);
        }
        pre.setNext(next);
        if(node==tail){
            tail=pre;
        }
        node.setNext(head);
        node.setPre(null);
        head.setPre(node);
        head = node;
    }

    public void put(int key, int value) {
        if(capacity==0){
            return;
        }
        Node node = cache.get(key);
        if (node != null) {
            node.setValue(value);
            recentUse(node);
            return;
        }
        node = new Node(key, value);

        if (cache.size() == capacity) {
            cache.remove(tail.getKey());
            tail=tail.getPre();
            if(tail!=null){
                tail.setNext(null);
            }
        }
        cache.put(key, node);
        if(head!=null){
            head.setPre(node);
        }
        node.setNext(head);
        node.setPre(null);
        head=node;
        if(tail==null){
            tail=node;
        }
    }
}

class Node{
    private int key;
    private int value;
    private Node pre,next;

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

    public Node getPre() {
        return pre;
    }

    public void setPre(Node pre) {
        this.pre = pre;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public int getKey() {
        return key;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

10.2 LFU(Least Frequently Used)

定义

最不经常使用(LFU)算法要求应支持以下两种操作:

  • get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
  • put(key, value) - 如果键不存在,请设置或插入值。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近 最少使用的键。
  • 可以在O(1)时间内完成以上两项操作。
示例
LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回 1
cache.put(3, 3);    // 去除 key 2
cache.get(2);       // 返回 -1 (未找到key 2)
cache.get(3);       // 返回 3
cache.put(4, 4);    // 去除 key 1
cache.get(1);       // 返回 -1 (未找到 key 1)
cache.get(3);       // 返回 3
cache.get(4);       // 返回 4
分析
  1. 为了可以在O(1)时间完成get(key)操作,很容易想到使用HashMap。
  2. 但这个算法要求当缓存达到容量时,可以删去最不经常使用的项,那么对于每一组(K,V)对象,都需要维护一个fred属性,记录被使用的次数。
  3. 算法即要求在O(1)时间内找到fred最小的项,考虑使用一个HashMap,他的Key是使用次数,Value是(K,V)对象,每操作一个KV对时,就将其升入下一个次数所对应的Value中。
  4. 算法要求使用次数相等时,要删去最近最少使用的键,因此可以在相同频率的K中维护一个双向链表,最近使用的对象放在头部,删去时直接删在尾部。
代码实现
public class LFU {
    
    private int capacity;
    private int minFred;

    private HashMap<Integer,Node> cache;
    private HashMap<Integer, LinkedList<Node>> fredMap;

    public LFU(int capacity) {
        this.capacity=capacity;
        cache = new HashMap<>(capacity);
        fredMap = new HashMap<>();
        minFred=0;
    }

    public int get(int key) {
        if(capacity==0){
            return -1;
        }
        Node node = cache.get(key);
        if(node==null){
            return -1;
        }
        fredIncrease(node);
        return node.getValue();
    }

    private void fredIncrease(Node node) {
        int fred = node.getFred();
        LinkedList<Node> fredList = fredMap.get(fred);
        fredList.remove(node);
        if(node.getFred()==minFred&&fredList.isEmpty()){
            minFred++;
        }
        fredList=fredMap.get(fred+1);
        if(fredList==null||fredList.size()==0){
            fredList=new LinkedList<>();
            fredMap.put(fred+1,fredList);
        }
        fredList.addFirst(node);
        node.fredInc();
    }

    public void put(int key, int value) {
        if(capacity==0){
            return;
        }
        Node node = cache.get(key);
        if (node != null) {
            node.setValue(value);
            fredIncrease(node);
            return;
        }
        node = new Node(key, value);

        if (cache.size() == capacity) {
            LinkedList<Node> fredList = fredMap.get(minFred);
            Node last = fredList.removeLast();
            cache.remove(last.getKey(),last);
        }
        cache.put(key, node);
        LinkedList<Node> fredList = fredMap.get(0);
        if (fredList == null) {
            fredList = new LinkedList<>();
            fredMap.put(0, fredList);
        }
        fredList.addFirst(node);
        minFred = 0;
        return;

    }
    
    public static void main(String[] args) {
        LFU cache =new LFU(3);
        cache.put(3, 1);
        cache.put(2, 1);
        System.out.println(cache.cache.keySet());
        cache.put(2, 2);
        System.out.println(cache.cache.keySet());

        cache.put(4, 4);
        System.out.println(cache.cache.keySet());

        int res1 = cache.get(2);
        System.out.println(res1);

        int res2 = cache.get(3);
        System.out.println(res2);

        int res3 = cache.get(2);
        System.out.println(res3);

        int res4 = cache.get(1);
        System.out.println(res4);

        cache.put(5, 5);

        int res5 = cache.get(1);
        System.out.println(res5);

        int res6 = cache.get(2);
        System.out.println(res6);

        int res7 = cache.get(3);
        System.out.println(res7);

        int res8 = cache.get(4);
        System.out.println(res8);

        int res9 = cache.get(5);
        System.out.println(res9);
    }
}

class Node{
    private int key;
    private int value;
    private int fred = 0;

    public Node(int key, int value) {
        this.key = key;
        this.value = value;
    }
    public void setValue(int value) {
        this.value = value;
    }

    public int getKey() {
        return key;
    }

    public int getValue() {
        return value;
    }

    public int getFred() {
        return fred;
    }

    public void fredInc(){
        fred++;
    }
}
发布了54 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/dong_W_/article/details/105337416