LruCache源码分析

在讲解源码之前,首先我们要知道什么是Lru?
LRU是Least Recently Used 近期最少使用算法。它的核心思想就是当缓存空间存满的时候,会优先淘汰那些近期最少使用的缓存对象。

我们来看看LruCache的结构:

private final LinkedHashMap<K, V> map;

/** Size of this cache in units. Not necessarily the number of elements. */
private int size;     //当前缓存的大小
private int maxSize;  //最大可缓存的大小

private int putCount;  //put方法被调用的次数
private int createCount;  //create()方法被调用的次数
private int evictionCount;  //调整的次数
private int hitCount;     //命中的次数
private int missCount;    //没有命中的次数

我们可以看到,原来LruCache底层封装了LinkedHashMap。LinkedHashMap继承自HashMap,不同的是LinkedHashMap维护的是一个双向链表。

    static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
        LinkedEntry<K, V> nxt;
        LinkedEntry<K, V> prv;

        /** Create the header entry */
        LinkedEntry() {
            super(null, null, 0, null);
            nxt = prv = this;
        }

        。。。。。。
    }

当要添加一个新的节点的时候,首先就会调用put方法:
这里put方法调用的是HashMap里面的put方法,但是放心,它最终会调用LinkedHashMap的addNewEntry方法

@Override 
public V put(K key, V value) {
        .........
        addNewEntry(key, value, hash, index);
        return null;
    }
@Override void addNewEntry(K key, V value, int hash, int index) {
        LinkedEntry<K, V> header = this.header;

        // Remove eldest entry if instructed to do so.
        LinkedEntry<K, V> eldest = header.nxt;
        if (eldest != header && removeEldestEntry(eldest)) {
            remove(eldest.key);
        }

        // Create new entry, link it on to list, and put it into table
        LinkedEntry<K, V> oldTail = header.prv;
        LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
                key, value, hash, table[index], header, oldTail);
        table[index] = oldTail.nxt = header.prv = newTail;
    }

这里写图片描述

当要获取的节点的时候,肯定就是要调用get方法

@Override public V get(Object key) {
        /*
         * This method is overridden to eliminate the need for a polymorphic
         * invocation in superclass at the expense of code duplication.
         */
        if (key == null) {
            HashMapEntry<K, V> e = entryForNullKey;
            if (e == null)
                return null;
            if (accessOrder)
                makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
                e != null; e = e.next) {
            K eKey = e.key;
            if (eKey == key || (e.hash == hash && key.equals(eKey))) {
                if (accessOrder)
                    makeTail((LinkedEntry<K, V>) e);
                return e.value;
            }
        }
        return null;
    }

基本的添加操作看代码基本可以看懂了,这里最重要的是makeTail()方法,那我们就来看看这个方法:

    private void makeTail(LinkedEntry<K, V> e) {
        // Unlink e
        e.prv.nxt = e.nxt;
        e.nxt.prv = e.prv;

        // Relink e as tail
        LinkedEntry<K, V> header = this.header;
        LinkedEntry<K, V> oldTail = header.prv;
        e.nxt = header;
        e.prv = oldTail;
        oldTail.nxt = header.prv = e;
        modCount++;
    }

原来这个get操作就是将get到的节点放到队列的尾部,这样也符合Lru的思想。
这个首先分两步,第一个是先创建节点,接着是放到队尾。
这里写图片描述
这里写图片描述

注意:

  • 列表不为空的时候,header.nxt指向第一个节点,header.pre指向尾节点
  • 列表空的时候,header.nxt和header.pre都指向自己
  • accessOrder是指明排序的方式,当为false的时候,是顺序插入,新加入的节点都是放到尾部,当为true的时候,若有访问或者更新节点的时候,该节点会放到尾部

讲完了LruCache的数据结构,我们正式讲解源码分析!
我们先看看LruCache的用法,我们以Bitmap为例:

private LruCache<String,Bitmap> mMemoryCache;
//实例化对象
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
            //计算每一个节点的占用内存大小
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };

//内存缓存的添加
mMemoryCache.put(key,bitmap)

//内存缓存的获取
mMemoryCache.get(key);

put方法:

    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            //若这个节点已经存在的话,就要减回刚才的添加的内存,因为假如存在的话,会返回一个value;若是新添加的节点,则会返回null
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
        //默认是空方法,自己可以在构造方法那些自己覆写该方法,主要是处理更新节点之后的操作
            entryRemoved(false, key, previous, value);
        }


        trimToSize(maxSize);
        return previous;
    }

添加节点的代码是进行了同步的,所以是线程安全的。这里最后调用了trimToSize(maxSize)方法。我们看看这个方法:

    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

这里是一个while(true)的死循环,当前缓存大小size小于规定的最大缓存的时候,就可以退出,否则就不断删除第一个节点,也是lru的核心所在。

Map.Entry<K, V> toEvict = map.entrySet().iterator().next();

get方法:

    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }

        /*
         * 自己构造新的值
         */

        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
        //添加的新值有冲突,需要通知已经进行了更新,这个方法也是为空的,自己在构造方法那里覆写
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //计算是否超出最大缓存
            trimToSize(maxSize);
            return createdValue;
        }
    }

若当前的key不存在链表当中,就调用create(key)方法,这个方法默认是返回null的,但是自己也是可以定义的,在构造方法那些自己进行覆写。若是构造了新的节点不为空的话,就相当于还要添加缓存大小,计算有没有超过最大缓存空间。

最后总结:

LruCache底层封装的是LinkedHashMap,当有节点进行更新或者访问的时候,都会将该节点放到尾部,当要添加节点的时候,就还要判断缓存空间是否有超出,如果超出的话,就不断删除第一个节点,直到缓存空间小于规定的最大缓存空间。

猜你喜欢

转载自blog.csdn.net/lihuanxing/article/details/53046598