LFU缓存

LFU算法

最近最少次数访问,缓存满的时候,优先淘汰掉最少访问的数据,若最少访问次数的数据有多个,淘汰掉访问时间与现在相隔最远的数据。

实现

堆 + 版本号

由于涉及到构建堆,所有操作的时间复杂度均为O(logN)

public class LFU{
    class Node{
        int key;
        int val;
        int count;
        int version;
        Node(int key, int val, int count){
            this.key = key;
            this.val = val;
            this.count = count;
        }
    }
    int capacity;
    int count;
    int version;
    public HashMap<Integer, Node> map;
    PriorityQueue<Node> heap;
    LFU(int capacity){
        this.capacity = capacity;
        this.count = 0;
        this.version = 0;
        map = new HashMap<>();
        heap = new PriorityQueue<Node>((node1, node2) ->{
            if (node1.count == node2.count)
                return node1.version - node2.version;
            return node1.count - node2.count;
        });
    }

    public void set(int key, int val){
        Node node = map.get(key);
        System.out.print("set key: " + key);
        if (node == null){
            if (count == capacity)
                removeLFU();
            node = new Node(key, val, 0);
            map.put(key, node);
            count++;
            node.count++;;
            node.version = version++;
            heap.add(node);
        } else {
            node.val = val;
            version++;
            node.count++;;
            node.version = version++;
            heap.remove(node);
            heap.add(node);
        }
        System.out.println();
    }

    public int get(int key){
        // System.out.println("get key: " + key);
        Node node = map.get(key);
        version++;
        if (node == null)
            return -1;
        else {
            node.count++;
            node.version = version;
            heap.remove(node);
            heap.add(node);
            return node.val;
        }
    }
    private void removeLFU() {
        // System.out.print("remove");
        Node node = heap.poll();
        if (node != null) {
            // System.out.print("remove key: " + node.key + " count: " + node.count);
            map.remove(node.key);
            count--;
        }
    }
}

双哈希表+LinkedList

set get 操作时间复杂度最优能到O(1),但是由于LinkedList的删除操作时间复杂度大于O(1),所以该版本的LFU缓存的操作的时间复杂度仍然大于O(1)。

/**
 * @author 49367
 * @date 2021/4/30 15:21
 */
public class LFU {
    class Node {
        int key;
        int val;
        // 访问次数
        int freq;

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

    HashMap<Integer, Node> nodeMap;
    HashMap<Integer, LinkedList<Node>> freqTable;
    // 容量,目前结点数量,最小访问次数
    int capacity, count, min;

    LFU(int capacity){
        nodeMap = new HashMap<>();
        freqTable = new HashMap<>();
        this.capacity = capacity;
        this.count = 0;
        this.min = 1;
    }

    public void set(int key, int val){
        if (capacity == 0)
            return;
        Node node = nodeMap.get(key);

        if (node == null){
            // 缓存已满,执行lfu策略
            if (count == capacity)
                removeLRU();
            node = new Node(key, val, 1);
            // 将结点加到nodeMap和freqMap中
            LinkedList<Node> list = freqTable.get(1);
            if (list == null)
                list = new LinkedList<>();
            list.addLast(node);
            // list可能是新建的,所以要put到table里
            freqTable.put(1, list);
            nodeMap.put(key, node);

            // 访问次数为1的链表不为空了,更新min为1
            this.min = 1;
            this.count++;
        } else {
            node.val = val;
            viewNode(node);
        }
    }

    public int get(int key){
        Node node = nodeMap.get(key);
        if (node == null)
            return -1;
        // node被访问的相关操作
        viewNode(node);
        return node.val;
    }

    /**
     * node被访问(set或get操作)
     * @param node
     */
    public void viewNode(Node node){
        /** node的访问次数加一,在freqTable中的位置应该改变 */
        // 首先从原来的链表中删掉
        LinkedList<Node> list = freqTable.get(node.freq);
        list.remove(node);

        // 如果min等于node原来的访问次数,且对应的链表已为空, min++
        if (node.freq == this.min && list.isEmpty())
            this.min++;

        node.freq++;

        // 放到新的访问次数的链表中去
        int newFreq = node.freq;
        LinkedList<Node> newList = freqTable.get(newFreq);
        if (newList == null)
            newList = new LinkedList<>();
        newList.addLast(node);
        freqTable.put(newFreq, newList);
    }

    /**
     * 根据lfu策略删除结点
     */
    private void removeLRU() {
        LinkedList<Node> list = freqTable.get(min);
        // min对应的链表头结点即为最近调用次数最少的node
        if (list != null){
            Node rmNode = list.removeFirst();
            nodeMap.remove(rmNode.key);
        }
        this.count--;
    }
}

双哈希表+自定义双向链表 (时间复杂度最低)

由于自定链表的删除操作的时间复杂度也是O(1),所以能够保证LFU缓存的set和get操作的时间复杂度都为O(1)。

public class LFU {
    class Node {
        int key;
        int val;
        // 访问次数
        int freq;

        Node pre, next;

        public Node(int key, int val, int freq) {
            this.key = key;
            this.val = val;
            this.freq = freq;
            this.pre = null;
            this.next = null;
        }

        public Node(){
            this.pre = null;
            this.next = null;
        }
    }


    class DoubleLinkedList{
        /**
         * 头结点和尾结点不为虚结点,节省空间
         */
        Node head;
        Node tail;

        public DoubleLinkedList(){
            this.head = null;
        }

        public void addFirst(Node node){
            Node oldHead = head;
            head = node;
            // 原头结点成为新头结点的下一个结点
            if (oldHead != null){
                head.next = oldHead;
                oldHead.pre = head;
            }
            // 如果尾结点为空,尾结点指向该node
            if (tail == null)
                tail = node;
        }
        public void addLast(Node node){
            Node oldTail = tail;
            tail = node;
            // 原头结点成为新头结点的下一个结点
            if (oldTail != null){
                tail.pre = oldTail;
                oldTail.next = tail;
            }
            // 如果头结点为空,则指向该node
            if (head == null)
                head = node;
        }

        public void remove(Node node){
            if (node.pre != null)
                node.pre.next = node.next;
            if (node.next != null)
                node.next.pre = node.pre;
            if (node == head)
                head = head.next;
            if (node == tail)
                tail = tail.pre;
            // 必须将node与链表彻底断开连接
            node.pre = null;
            node.next = null;
        }

        public boolean isEmpty(){
            return head == null;
        }

        public Node removeFirst() {
            if (head == null)
                return null;
            Node res = head;
            // 将该结点从链表中删去
            remove(res);

            return res;
        }
    }

    HashMap<Integer, Node> nodeMap;
    HashMap<Integer, DoubleLinkedList> freqTable;
    // 容量,目前结点数量,最小访问次数
    int capacity, count, min;

    LFU(int capacity){
        nodeMap = new HashMap<>();
        freqTable = new HashMap<>();
        this.capacity = capacity;
        this.count = 0;
        this.min = 1;
    }

    public void set(int key, int val){
        if (capacity == 0)
            return;
        Node node = nodeMap.get(key);

        if (node == null){
            // 缓存已满,执行lfu策略
            if (count == capacity)
                removeLRU();
            node = new Node(key, val, 1);
            // 将结点加到nodeMap和freqMap中
            DoubleLinkedList list = freqTable.get(1);
            if (list == null)
                list = new DoubleLinkedList();
            list.addLast(node);
            // list可能是新建的,所以要put到table里
            freqTable.put(1, list);
            nodeMap.put(key, node);

            // 访问次数为1的链表不为空了,更新min为1
            this.min = 1;
            this.count++;
        } else {
            node.val = val;
            viewNode(node);
        }
    }

    public int get(int key){
        Node node = nodeMap.get(key);
        if (node == null)
            return -1;
        // node被访问的相关操作
        viewNode(node);
        return node.val;
    }

    /**
     * node被访问(set或get操作)
     * @param node
     */
    public void viewNode(Node node){
        /** node的访问次数加一,在freqTable中的位置应该改变 */
        // 首先从原来的链表中删掉
        DoubleLinkedList list = freqTable.get(node.freq);
        list.remove(node);

        // 如果min等于node原来的访问次数,且对应的链表已为空, min++
        if (node.freq == this.min && list.isEmpty())
            this.min++;

        node.freq++;

        // 放到新的访问次数的链表中去
        int newFreq = node.freq;
        DoubleLinkedList newList = freqTable.get(newFreq);
        if (newList == null)
            newList = new DoubleLinkedList();
        newList.addLast(node);
        freqTable.put(newFreq, newList);
    }

    /**
     * 根据lfu策略删除结点
     */
    private void removeLRU() {
        DoubleLinkedList list = freqTable.get(min);
        // min对应的链表头结点即为最近调用次数最少的node
        if (list != null){
            Node rmNode = list.removeFirst();
            nodeMap.remove(rmNode.key);
        }
        this.count--;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值