美文网首页
LRU手撕实现

LRU手撕实现

作者: 江北晓白 | 来源:发表于2019-10-05 08:54 被阅读0次

    国庆期间,因玩忘学,愧疚难当,一时冲动,手撕算法,以表歉意。谈起LRU,实则对其心仪已久,之前也对其实现原理进行过粗略的理解,也接触过相关应用:如经典的redis的淘汰策略、mybatis的二级缓存策略中都对其有过应用(LRU算法算是应用较广的保留热点数据的方法),但真正的讲起其原理总是略有差之毫厘之感(博客中的实现有多种),今天借着愧疚的冲动,具体实现一下当前已知的最优的实现。
    LRU的基本实现是将热点数据移动到队列头,这样不常用的数据就会处于队列尾,常用的实现有数组、双向链表,但就查询效率而已,hashmap有较好的查询效率(O(1)级别),而双向链表又能够保证插入顺序,基于Hashmap+双向链表可以实现效率较高的LRU。
    一、Hashmap+双向链表实现
    基本的Hashmap+双向链表实现如下,nodes作为一个hashmap通过不断添加key、value(链表节点),而value间维持一个双向链表,这样就能保证维持capacity容量的
    双向链表,而每个key对应的节点都存储在Hashmap里。但是,可能也会存在一个问题,
    就是如果插入较多数据后,因为产生了大量的node在hashmap里没有被使用到,可能会产生内存泄露。

    public class LRU<K,V> {
        private int capacity;
        private int size;
        private Entry<K,V> head;
        private Entry<K,V> tail;
        private class Entry<K,V>  extends SoftReference<K> {
            Entry(K key, V value){
                super(key);
                this.key = key;
                this.value = value;
            }
            private K key;
            private V value;
            private Entry<K,V> prev;
            private Entry<K,V> next;
        }
        private HashMap<K , Entry<K,V>> nodes = new ConcurrentHashMap<>();
        LRU(int capacity){
            this.capacity = capacity;
            size = 0;
            head=null;
            tail=null;
            head.prev = head;
            head.next = tail;
            tail.next = tail;
            tail.prev = head;
        }
        public void put(K key, V value){
            Entry<K,V> node = new Entry<K, V>(key, value);
            if(nodes.get(key) != null ){
                Entry<K,V> tempNode = nodes.get(key);
                Entry<K,V> prevNode = tempNode.prev;
                Entry<K,V> nextNode = tempNode.next;
                prevNode.next = nextNode;
                nextNode.prev = prevNode;
                node.prev = node;
                node.next = head;
                head = node;
            } else if(size >= capacity){
                Entry<K,V> prevNode = tail.prev;
                prevNode.next = node;
                node.prev = prevNode;
                node.next = node;
                tail = node;
            } else{
                if(size == 0){
                    head = node;
                    tail = node;
                } else{
                    node.prev = tail;
                    node.next = node;
                    tail = node;
                }
                size ++;
            }
            nodes.put(key,node);
        }
        public V get(K key){
            if(nodes.get(key) != null){
                Entry<K,V> tempNode = nodes.get(key);
                Entry<K,V> prevNode = tempNode.prev;
                Entry<K,V> nextNode = tempNode.next;
                prevNode.next = nextNode;
                nextNode.prev = prevNode;
                tempNode.prev = tempNode;
                tempNode.next = head;
                head = tempNode;
                nodes.put(key,tempNode);
            }
            return null;
        }
        public List<V> printAllValue(){
            List<V> values = new ArrayList<V>();
            Entry<K,V> node = head;
            while(true){
                values.add(node.value);
                if(node == tail){
                    break;
                }
                node = node.next;
            }
            return values;
        }
    }
    

    二、LinkedHashmap实现
    JDk原生LinkedHashmap的实现中,已经实现了LRU清除非热点数据的逻辑,LinkedHashMap的实现与一中大同小异,其具体表现为重写了Hashmap的afterNodeInsertion、afterNodeAccess方法,由于这个特性,可以封装linkedHashmap,实现LRU,原理见一,实现大同小异,相对简单。

    void afterNodeInsertion(boolean evict) { // possibly remove eldest
            LinkedHashMap.Entry<K,V> first;
            if (evict && (first = head) != null && removeEldestEntry(first)) {
                K key = first.key;
                removeNode(hash(key), key, null, false, true);
            }
        }
    void afterNodeAccess(Node<K,V> e) { // move node to last
            LinkedHashMap.Entry<K,V> last;
            if (accessOrder && (last = tail) != e) {
                LinkedHashMap.Entry<K,V> p =
                    (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
                p.after = null;
                if (b == null)
                    head = a;
                else
                    b.after = a;
                if (a != null)
                    a.before = b;
                else
                    last = b;
                if (last == null)
                    head = p;
                else {
                    p.before = last;
                    last.after = p;
                }
                tail = p;
                ++modCount;
            }
        }
    

    三、缓存污染
    缓存污染,当LRU遭遇偶发性、周期性批量操作,热点数据的准确率会被破坏。

    相关文章

      网友评论

          本文标题:LRU手撕实现

          本文链接:https://www.haomeiwen.com/subject/hxgspctx.html