LRU手撕实现

2019-10-05  本文已影响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遭遇偶发性、周期性批量操作,热点数据的准确率会被破坏。

上一篇下一篇

猜你喜欢

热点阅读