zoukankan      html  css  js  c++  java
  • LRU Cache java实现

    要求:

    • get(key):如果key在cache中,则返回对应的value值,否则返回null
    • set(key,value):如果key不在cache中,则将该(key,value)插入cache中(注意,如果cache已满,则必须把最近最久未使用的元素从cache中删除);如果key在cache中,则重置value的值。
    • set和get的时间复杂度都是O(1)。

    两个map

    
    /**
     * 思路:时间复杂度是O(1),一下子想到的是map。但是怎么进行淘汰呢?需要记录时间,且查找的复杂度也是O(1),那也用map吧。
     * put之前,需要先查看是否包含key,如果包含,删掉之前的数据,再放进新的数据,且把key的访问时间更新。淘汰最早访问数据的时候,还需要根据访问时间查询key。
     * 所以,需要的操作包括:根据key获取value和access;根据access获取key。
     */
    public class MyLRUMapSample<K, V> {
            //cache的容量
            private int capacity;
            //存储key-value/access的map
            private HashMap<K, Pair<V>> valueMap;
            //存储access-key的map,使用SortedMap,对access进行排序
            private SortedMap<Long, K> accessKeyMap;
    
            public MyLRUMapSample(){}
            public MyLRUMapSample(int capacity){
                this.capacity = capacity;
                valueMap = new HashMap<K, Pair<V>>();
                accessKeyMap = new TreeMap<Long, K>();
            }
    
            public V get(K key){
                if(!valueMap.containsKey(key)){
                    return null;
                }
                //修改access
                Long access = accessKeyMap.lastKey() + 1;
                accessKeyMap.remove(valueMap.get(key).access);
                accessKeyMap.put(access, key);
                valueMap.get(key).access = access;
                return valueMap.get(key).value;
            }
    
            public void put(K key, V value){
                //如果含有值,则更新
                if(valueMap.containsKey(key)){
                    Long oldAccess = valueMap.get(key).access;
                    accessKeyMap.remove(oldAccess);
                    valueMap.remove(key);
                }
    
                //如果存储已满,则淘汰掉最早访问的
                if(valueMap.size() >= capacity){
                    Long oldAccess = accessKeyMap.firstKey();
                    valueMap.remove(accessKeyMap.get(oldAccess));
                    accessKeyMap.remove(oldAccess);
                }
    
                //添加最新的数据
                Long access = accessKeyMap.isEmpty() ? 0 : accessKeyMap.lastKey() + 1;
                valueMap.put(key, new Pair(access, value));
                accessKeyMap.put(access, key);
            }
    
            //访问时间和value值
            class Pair<V>{
                public Long access;
                public V value;
                public Pair(){}
                public Pair(Long access, V value){
                    this.access = access;
                    this.value = value;
                }
            }
    
        public K getOldestKey(){
            return accessKeyMap.isEmpty() ? null : accessKeyMap.get(accessKeyMap.firstKey());
        }
    
    
        public K getLatestKey(){
            return accessKeyMap.isEmpty() ? null : accessKeyMap.get(accessKeyMap.lastKey());
        }
    
    
        public static void main(String[] args) {
            LinkedListMapLRUSample<Integer, Integer> sample = new LinkedListMapLRUSample<Integer, Integer>(2);
            Assert.check(sample.getLatestKey() == null);
            Assert.check(sample.get(2) == null);
            sample.put(2, 1);
            sample.put(2, 2);
            Assert.check(sample.get(2).intValue() == 2);
            sample.put(1, 2);
            sample.put(1, 3);
            Assert.check(sample.get(1) == 3);
            Assert.check(sample.get(2).intValue() == 2);
    
    		sample.put(3, 3);
            Assert.check(sample.get(1) == null);
        }
    
    
    }
    
    

    双向链表+hashMap

    /**
     * 使用LinkedList存储数据,从头部插入,从尾部淘汰。这样就保证了容量和淘汰规则正确性。
     * 使用hashMap,通过key找到value。
     */
    public class LinkedListMapLRUSample<K,V> {
    
        class Node<K, V>{
            public K key;
            public V value;
            public Node preNode;
            public Node nextNode;
            public Node(){}
            public Node(K key, V value, Node preNode, Node nextNode){
                this.key = key;
                this.value = value;
                this.preNode = preNode;
                this.nextNode = nextNode;
            }
        }
    
        private Node<K,V> head;
        private Node<K,V> tail;
        private HashMap<K,Node<K, V>> valueMap;
        private int capacity;
    
        public LinkedListMapLRUSample(){}
        public LinkedListMapLRUSample(int capacity){
            this.capacity = capacity;
            valueMap = new HashMap<K,Node<K, V>>();
        }
    
        public void put(K key, V value){
            if(head == null){
                head = new Node(key, value, null, null);
                tail = head;
                valueMap.put(key, head);
                return;
            }
    
            //如果已经包含了数据,需要更新
            if(valueMap.containsKey(key)){
                valueMap.get(key).value = value;
                moveToHead(key);
                return;
            }
    
            //满容量,需要淘汰掉最旧的数据
            if(valueMap.size() >= capacity){
                valueMap.remove(tail.key);
                tail = tail.preNode;
                if(tail != null){
                    tail.nextNode = null;
                }
            }
    
            insertAsHead(key, value);
    
        }
    
        private void insertAsHead(K key, V value){
            //在头部添加新节点
            Node target = new Node(key, value, null, head);
            if(head != null){
                head.preNode = target;
                target.nextNode = head;
            }
            valueMap.put(key, target);
            head = target;
        }
    
        private void moveToHead(K key){
            Node target = valueMap.get(key);
            //头部元素
            if(target.preNode == null){
                return;
            }
            //尾部元素
            if(target.nextNode == null){
                tail = target.preNode;
                tail.nextNode = null;
            }else{
                target.preNode.nextNode = target.nextNode;
                target.nextNode.preNode = target.preNode;
            }
    
            target.preNode = null;
            target.nextNode = head;
            head.preNode = target;
            head = target;
        }
    
        public V get(K key){
            if(!valueMap.containsKey(key)){
                return null;
            }
            moveToHead(key);
            return valueMap.get(key).value;
        }
    
        public K getLatestKey(){
            return head == null ? null : head.key;
        }
    
        public K getOldestKey(){
            return tail == null ? null : tail.key;
        }
    
        public static void main(String[] args) {
            LinkedListMapLRUSample<Integer, Integer> sample = new LinkedListMapLRUSample<Integer, Integer>(2);
            Assert.check(sample.getLatestKey() == null);
            Assert.check(sample.get(2) == null);
            sample.put(2,1);
            sample.put(2, 2);
            Assert.check(sample.get(2).intValue() == 2);
            sample.put(1, 2);
            sample.put(1, 3);
            Assert.check(sample.get(1) == 3);
            Assert.check(sample.get(2).intValue() == 2);
    
            sample.put(3, 3);
            Assert.check(sample.get(1) == null);
        }
    }
    
    

    需要根据访问时间或者插入时间进行排序时,考虑使用双向链表。

    最先插入淘汰和最少访问淘汰

    • 最先插入淘汰,即FIFO。也就是要求set操作时,新数据放在head,get操作不需要移动。那么,只需要在get操作的时候直接返回valueMap中的数据即可。
    • 最少访问淘汰,是按照访问次数排序,将访问次数最少的数据删除。
      可以使用两个map实现,valueMap(HashMap)和accessCountMap(SortedMap)。accessCount初始为1,get/put时,accessCount自增,并按照accessCount排序。

    如果用数组和map实现,就需要accessCount自增后,进行重排序;或者在需要淘汰的时候进行重排序。

    LinkedHashMap源码解读

    LinkedHashMap使用一个双向链表加一个HashMap。同时有一个成员变量accessOrder控制淘汰策略:为true 表示按照LRU方式淘汰,false表示按照FIFO方式淘汰。
    另外还有一个方法控制是否删除最旧的数据:

        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
            return false;
        }
    
       //put新数据的时候,检查是否需要删除最旧的数据
    	void addEntry(int hash, K key, V value, int bucketIndex) {
            super.addEntry(hash, key, value, bucketIndex);
    
            // Remove eldest entry if instructed
            Entry<K,V> eldest = header.after;
            if (removeEldestEntry(eldest)) {
                removeEntryForKey(eldest.key);
            }
        }
    

    使用LinkedHashMap实现LRUCache:

    public class LinkedHashMapLRUSample<K, V> extends LinkedHashMap<K, V>{
        private int capacity;
        public LinkedHashMapLRUSample(){}
        public LinkedHashMapLRUSample(int capacity){
            super(16, 0.75f, true);
            this.capacity = capacity;
        }
    
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return this.size() > capacity;
        }
    
        public static void main(String[] args) {
            LinkedHashMapLRUSample<Integer, Integer> sample = new LinkedHashMapLRUSample<Integer, Integer>(2);
            Assert.check(sample.get(2) == null);
            sample.put(2,1);
            sample.put(2, 2);
            Assert.check(sample.get(2).intValue() == 2);
            sample.put(1, 3);
            Assert.check(sample.get(1) == 3);
            Assert.check(sample.get(2).intValue() == 2);
    
            sample.put(3, 3);
            Assert.check(sample.get(1) == null);
        }
    }
    

    参考

    缓存算法和实现 : 对各种缓存算法做了说明。
    LRU算法机器变种

  • 相关阅读:
    SSM配置后可以访问静态html文件但无法访问其他后台接口的解决方案
    使用Idea部署SSM项目后,访问路径为url:8080/项目名_war_exploded的解决方案
    gym102302E_Chi's performance
    Linux命令之wc(Word Count)
    【贪心算法】最大整数
    贪心算法讲解及例题
    【贪心算法】均分纸牌
    【贪心算法】背包问题
    【基础题】字符串替换
    【基础题】对称排序
  • 原文地址:https://www.cnblogs.com/shoren/p/LRUCache.html
Copyright © 2011-2022 走看看