LRUCache使用

1、业务场景:

由于业务场景是需要查询团单和门店的固定品类,而这些信息比较固定、不容易变化,为了减少对下游的压力,使用了一个本地缓存druid的LRUCache

private static LRUCache dealCatCache = new LRUCache(Integer.parseInt(size),16, 0.75f, true);

2、源码:

public class LRUCache extends LinkedHashMap {

    private static final long serialVersionUID = 1L;
    private final int         maxSize;

    public LRUCache(int maxSize){
        this(maxSize, 16, 0.75f, false);
    }

    public LRUCache(int maxSize, int initialCapacity, float loadFactor, boolean accessOrder){
        super(initialCapacity, loadFactor, accessOrder);
        this.maxSize = maxSize;
    }

    protected boolean removeEldestEntry(Map.Entry eldest) {
        return this.size() > this.maxSize;
    }
}

可以看出LRUCache是通过继承了LinkedHashMap这一个双向链表来实现LRU算法的。
来看下super(initialCapacity, loadFactor, accessOrder);

 public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

LinkedHashMap重写了init方法

    void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

可以看到LinkedHashMap始终维持了一个header不放在数组中,就是用来指示开始元素和标志结束元素的。
接下来看看put操作,首先进入hashmap的public V put(K key, V value) 方法

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

hashmap的put操作是先看table数组里面是否有该key,有的话把old value替换为新的value同时执行LinkedHashMap中的recordAccess方法,没有该key,也是在LRU算法的情况下把该key移到链表尾部(header节点的前一个节点)。

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

boolean accessOrder是表示该LinkedHashMap是FIFO(false)还是LRU(true)策略进出元素的。
remove(); addBefore(lm.header);代码如下

private void remove() {
            before.after = after;
            after.before = before;
        }
private void addBefore(Entry existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

可以看到,先执行remove把该元素从链表抹去,然后把header元素和该元素before和after关联起来,这样就实现在put已有key的情况下,把对应key的元素移到链表尾部了。接下来再看如果没有改key的操作

LinkedHashMap中
void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed
        Entry eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }
HashMap中
void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry old = table[bucketIndex];
        Entry e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

addEntry代码和hashmap一致,重写了createEntry代码,多做了addBefore操作,把header和新加的元素建立首尾关联
接下来看看get方法

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

同样执行了recordAccess,所以不再赘述。

总结一下LRU的本质:插入时会正常插入HashMap存储数据,同时LinkedHashMap会维护一个双向队列记录尾节点header,没插入一个entry,都把它插入到header的前一位,如果对应key存在,则替换value值,把该entry插入到header前一位;如果做get操作,也把该key-value插入到header前一位。在插入过程中会进入removeEldestEntry方法判断是否要移除最老的元素,确定移除,则把header后一位元素移除。

你可能感兴趣的:(LRUCache使用)