内存替换算法——LRU

        在我们内存或者cache中有两种替换算法来保证内存或者cache中都是“热点”的数据,一个是LRU和LFU。这里面们先介绍LRU,在这里的get和set操作的都是O(1),因为这两种速度都是很快的,不能要求O(n)。

【问题】

        设计一种缓存结构,该结构在构造时确定大小,假设大小为K,并有两个功能:

                set(key, value):将记录(key, value)插入该结构。

                get(key):返回key对应的value值。

【要求】

1.set和get方法的时间复杂度为O(1)。

2.某个key的set或get操作一旦发生,认为这个key的记录成了最经常使用的。

3.当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。

【举例】

假设缓存结构的实例是cache,大小为3,并依次发生如下行为:

1.cache.set("A", 1)。最经常使用的记录为("A", 1)。

2.cache.set("B", 2)。最经常使用的记录为("B", 2),("A", 1)变为最不经常的。

3.cache.set("C", 3)。最经常使用的记录为("C", 3),("A", 1)还是最不经常的。

4.cache.get("A")。最经常使用的记录为("A", 1),("B", 2)变为最不经常的。

5.cache.set("D", 4)。大小超过了3,所以移除此时最不经常使用的记录("B", 2),

加入记录("D", 4),并且为最经常使用的记录,然后("C", 3)变为最不经常使用的记录。

【题解】

        储存数据用hash_map,因为hash表的增删该查的复杂度都为O(1)

        本来使用频率使用队列存储,使用的放在头,不使用的自动向后排,最不经常使用的在队尾

但每次从队列中取出正在使用的数据至队列头部的复杂度为O(N),不满足条件。所以只能使用双向链表来进行存储,hash表中存放着每个数据节点的地址参数,故链表的中数据位置调整复杂度为O(1)。最经常使用的数据节点在链表的尾部,不经常使用的在链表的尾部,因为缓存数据时需要经常使用的,而链表的尾部插入更加方便。

【代码】

1.基础数据结构Node(节点)

    public static class Node<K, V> {
        public K key;
        public V value;
        public Node<K, V> last;
        public Node<K, V> next;

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

2.基础数据结构NodeDoubleLinkedList(双向链表)

        addNode方法表示在双向链表中从尾部添加一个新的节点。

        moveNodeToTail方法使用前必须保证传入的Node节点在双向链表中,该方法表示把双向链表中的节点放在链表最后的操作。

        removeHead方法表示删除双向链表的头结点。

    public static class NodeDoubleLinkedList<K, V> {
        private Node<K, V> head;
        private Node<K, V> tail;

        public NodeDoubleLinkedList() {
            head = null;
            tail = null;
        }

        public void addNode(Node<K, V> newNode) {
            if (newNode == null) {
                return;
            }
            if (head == null) {
                head = newNode;
                tail = newNode;
            } else {
                tail.next = newNode;
                newNode.last = tail;
                tail = newNode;
            }
        }

        public void moveNodeToTail(Node<K, V> node) {
            if (this.tail == node) {
                return;
            }
            if (this.head == node) {
                this.head = node.next;
                this.head.last = null;
            } else {
                node.last.next = node.next;
                node.next.last = node.last;
            }
            node.last = this.tail;
            node.next = null;
            this.tail.next = node;
            this.tail = node;
        }


        public Node<K, V> removeHead() {
            if (this.head == null) {
                return null;
            }
            Node<K, V> res = this.head;
            if (this.head == this.tail) {
                this.head = null;
                this.tail = null;
            } else {
                this.head = res.next;
                res.next = null;
                this.head.last = null;
            }
            return res;
        }
    }

3.基础数据结构MyCache(自定义缓存)

        MyCache构造方法,自定义的缓存大小是固定不变的。定义一个HashMap结构keyNodeMap

用于查看K是否之前就有,V表示Node节点。再定义一个双向链表nodeList。

        get方法表示如果HashMap结构中之前存在这个K,就把这个K所对应的节点在双向链表中的位置放在最后。

        removeMostUnusedCache方法表示移除双向链表的头节点以及HashMap中的对应关系。

        set方法表示如果之前的HashMap结构存储过这个对应关系,就修改这个节点的值,并且把这个节点在双向链表中的位置放在最后。如果之前的HashMap结构没有存储过这个对应关系,并且当前缓存容量未超过设定的值,则新建Node节点并且在HashMap结构和双向链表中添加对应关系。缓存容量超过设定值则使用 removeMostUnusedCache方法删除双向链表的头节点。

    public static class MyCache<K, V> {
        private HashMap<K, Node<K, V>> keyNodeMap;
        private NodeDoubleLinkedList<K, V> nodeList;
        private final int capacity;

        public MyCache(int cap) {
            if (cap < 1) {
                throw new RuntimeException("should be more than 0.");
            }
            keyNodeMap = new HashMap<K, Node<K, V>>();
            nodeList = new NodeDoubleLinkedList<K, V>();
            capacity = cap;
        }

        public V get(K key) {
            if (keyNodeMap.containsKey(key)) {
                Node<K, V> res = keyNodeMap.get(key);
                nodeList.moveNodeToTail(res);
                return res.value;
            }
            return null;
        }

        public void set(K key, V value) {
            if (keyNodeMap.containsKey(key)) {
                Node<K, V> node = keyNodeMap.get(key);
                node.value = value;
                nodeList.moveNodeToTail(node);
            } else {

                if (keyNodeMap.size() == capacity) {
                    removeMostUnusedCache();
                }

                Node<K, V> newNode = new Node<K, V>(key, value);
                keyNodeMap.put(key, newNode);
                nodeList.addNode(newNode);

            }
        }

        private void removeMostUnusedCache() {
            Node<K, V> removeNode = nodeList.removeHead();
            keyNodeMap.remove(removeNode.key);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值