zoukankan      html  css  js  c++  java
  • LRU算法

    本文 利用双链表+hashMap实现LRU算法 

    为什么要选双链表+HashMap?

    因为,LRU的核心是“最近最少使用”,主要的功能有:

    1. 在get数据时,先获取到数据,然后会将该数据放到链表最后位置,方便下次取。(get时可以先到链表尾部取,看是不是)

    2. 在put数据时,首先看链表中有没有,有就将这个数据放到链表尾部,方便下次取,没有就在链表尾部插入。

    3. 在put数据时,看存入数量满了没,满了就删除链表头部的元素(即删除最老元素)

    因此:

    使用HashMap的原因:都有查询数据的操作,使用HashMap可以快速查到有没有数据,

    使用双向链表的原因:有插入与删除的操作,选用Linked双向链表更有优势。

    代码:

    package Algorithm.LRU;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * LRU(Least Recently Used)即“最近最少使用”,这里使用双链表+hashMap实现了LRU算法
     * 注意:LRUCache中没有重复的节点
     *
     * @author chenjunjie
     * @since 2018-04-25
     */
    public class LRUCache {
        Node head, tail;
        int size;
        int capacity;
        Map<Integer,Node> map;
    
        /**
         * 定义内部类,Node节点
         */
        static class Node {
            int key;
            int val;
            Node pre;
            Node post;
    
            /**
             * 构造函数
             * @param key Map的key
             * @param val May的value
             */
            Node(int key, int val) {
                this.key = key;
                this.val = val;
            }
        }
    
        /**
         * 构造函数,初始化
         * @param capacity
         */
        public LRUCache(int capacity) {
            this.head = null;
            this.tail = null;
            this.size = 0;
            this.capacity = capacity;
            // 使用了HashMap
            // 注意这里Map中的value为Node类型,这样可以方便索引到某个具体node
            this.map = new HashMap<Integer, Node>();
        }
    
        /**
         * 获取最近使用node
         * 先remove掉节点以前所处位置,然后添加到链表的尾部,从而达到最近使用的效果
         * @param key
         * @return 返回value
         */
        public int get(int key) {
            if(!map.containsKey(key)) {
                return -1;
            }
            Node temp = map.get(key);
            remove(temp);
            add(temp);
            return temp.val;
        }
    
        /**
         * 添加新节点,并保证放入最后节点
         * @param key map的key值
         * @param value map中的value
         */
        public void put(int key, int value) {
            Node temp;
            // 如果之前有key,但不是放在链表尾部,则先删除然后添加到尾部
            if(map.containsKey(key)){
                temp = map.get(key);
                remove(temp);
                map.remove(key);
                size--;
            }
    
            // 如果超过了容量,这删除头部节点,即删除“最老”节点
            if(size == capacity){
                Node h = head;
                remove(h);
                map.remove(h.key);
                size--;
            }
    
            temp = new Node(key,value);
            // 链表中添加Node
            add(temp);
            // HashMap添加Entry
            map.put(key,temp);
            size++;
        }
    
        /**
         * 普通的linked链表删除操作
         * @param n 要删除的节点
         */
        private void remove(Node n) {
            // 如果只有一个节点
            if(n==head && n==tail){
                head = null;
                tail = null;
            }
            // 如果是头节点
            else if(n == head) {
                head = head.post;
                head.pre = null;
            }
            // 如果是尾节点
            else if(n == tail) {
                tail = tail.pre;
                tail.post = null;
            }
            //
            else{
                n.pre.post = n.post;
                n.post.pre = n.pre;
            }
            n.pre = null;
            n.post = null;
        }
    
        /**
         * 在链表尾部加入节点
         * @param n 添加节点
         */
        private void add(Node n) {
            if(head == null && tail == null) {
                head = n;
                tail = n;
            }else {
                tail.post = n;
                n.pre = tail;
                tail = n;
            }
        }
    
        /**
         * getMap,方便测试遍历查询结果
         * @return
         */
        public Map<Integer, Node> getMap() {
            return map;
        }
    
        /**
         * getHead,方便测试遍历查询结果,实际上不要暴露出来
         * @return
         */
        public Node getHead(){
            return head;
        }
    
    
    }

    测试:

    public static void main (String[] args) {
        LRUCache lurCache = new LRUCache(10);
        int N = 12 ; //7
        int i = 1;
        while (i<N){
            lurCache.put(i,i*2);
            i++;
        }
    
        System.out.println(lurCache.get(4));
        lurCache.put(4,666);
    
        // 遍历map
        Map hsMap = lurCache.getMap();
        Iterator it = hsMap.entrySet().iterator();
        while(it.hasNext()){
            Map.Entry<Integer,LRUCache.Node> result = (Map.Entry<Integer,LRUCache.Node>)it.next();
            System.out.printf("(key=%d,value=%d)
    ",result.getKey(),result.getValue().val);
        }
    
        System.out.println("-----------");
    
        // 遍历node
        LRUCache.Node n = lurCache.getHead();
        LRUCache.Node cur = n;
        while(cur != null){
            System.out.printf("(key=%d,value=%d)
    ",cur.key,cur.val);
            cur = cur.post;
        }
    }

    结果如下:

    特性:

    1. 插入了12条数据,由于定义的容量为10,因此每次满容量时删除头结点的数据。

    2. 修改key=4的值,修改后放到队列尾部

    3. 使用get()后,node节点的数据也会放到队列尾部(ps:测试代码中没有写)

    扩展(待续):

    /**
    * LRU是Least Recently Used 的缩写,即“最近最少使用”
    * 扩展 LinkedHashMap 可以实现就实现了LRU,
    * LinkedHashMap中最近读取的会放在最前面,最最不常读取的会放在最后,同时
    * 调用removeEldestEntry会移除链表中“最老”的节点,但注意源码中调用该方法返回的值为false
    * 因此,可以继承LinkedHashMap重写removeEldestEntry, 从而构建LRU缓存
    */
  • 相关阅读:
    《A First Course in Mathematical Modeling》-chaper1-差分方程建模
    《数学竞赛辅导》-一元函数积分学-7.24
    《University Calculus》-chape4-导数的应用-极值点的二阶导数检验法
    《A First Course in Probability》-chaper1-组合分析-方程整数解的个数
    《训练指南》——7.24
    《数学竞赛辅导》-一元函数微分学-7.23
    《University Calculus》-chape4-导数的应用-微分中值定理
    《训练指南》——7.21
    #424 Div2 E
    #424 Div2 C
  • 原文地址:https://www.cnblogs.com/chenjunjie12321/p/8945246.html
Copyright © 2011-2022 走看看