LinkedHashMap实现LRU算法

LinkedHashMap特别有意思,它不单单是在HashMap上增长Entry的双向连接,它更能借助此特性实现保证Iterator迭代按照插入顺序(以insert模式建立LinkedHashMap)或者实现LRU(Least Recently Used最近最少算法,以access模式建立LinkedHashMap)。算法

下面是LinkedHashMap的get方法的代码函数

public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }

其中有一段:e.recordAccess(this)。下面咱们进入Entry的定义性能

void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

这里的addBefore(lm.header)是作什么呢?再看this

private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

从这里能够看到了,addBefore(lm.header)是把当前访问的元素挪到head的前面,即最近访问的元素被放到了链表头,如此要实现LRU算法只须要从链表末尾往前删除就能够了,多么巧妙的方法。spa

在看到LinkedHashMap以前,我觉得实现LRU算法是在每一个元素内部维护一个计数器,访问一次自增一次,计数器最小的会被移除。可是要想到,每次add的时候都须要作这么一次遍历循环,并取出最小的抛弃,在HashMap较大的时候效率不好。固然也有其余方法来改进,好比创建<访问次数,LinkedHashMap元素的key>这样的TreeMap,在add的时候往TreeMap里也插入一份,删除的时候取最小的便可,改进了效率但没有LinkedHashMap内部的默认实现来的简捷。code

LinkedHashMap是何时删除的呢?对象

 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);
        }
    }

在增长Entry的时候,经过removeEldestEntry(eldest)判断是否须要删除最老的Entry,若是须要则remove。注意看这里Entry<K,V> eldest=header.after,记得咱们前面提过LinkedHashMap还维护一个双向链表,这里的header.after就是链表尾部最后一个元素(头部元素是head.before)。blog

LinkedHashMap默认的removeEldestEntry方法以下排序

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
老是返回false,因此开发者须要实现LRU算法只须要继承LinkedHashMap并重写removeEldestEntry方法,下面以MyBatis的LRU算法的实现举例
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;
 
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };

开发者的子类并不须要直接操做eldest(上例中得到eldestKey只是MyBatis须要映射到Cache对象中的元素),只要根据本身的条件(通常是元素个数是否到达阈值)返回true/false便可。注意,要按照LRU排序必须在new LinkedHashMap()的构造函数的最后一个参数传入true(true表明LinkedHashMap内部的双向链表按访问顺序排序,false表明按插入顺序排序)。继承

在LinkedHashMap的注释里明确提到,该类在保持插入顺序、不想HashMap那样混乱的状况下,又没有像TreeMap那样的性能损耗。同时又可以很巧妙地实现LRU算法。其余方面和HashMap功能一致。有兴趣的同窗能够仔细看看LinkedHashMap的实现。

相关文章
相关标签/搜索