LruCache了解一下

一、LruCache是什么

该类最开始出现在Android 3.1中,是高速缓存,其中包含对有限数量的值的强引用。每次值被访问时,它都会移到队列的开头。将值添加到缓存中后如果缓存满了,该队列末尾的值将被逐出,并且可能会引发垃圾回收。
默认情况下,缓存大小以条目数衡量。重写 sizeOf以不同的单位调整缓存大小。如下例,此缓存限制存放4MiB的Bitmap:

    int cacheSize = 4 * 1024 * 1024; // 4MiB
    LruCache bitmapCache = new LruCache(cacheSize) {
        protected int sizeOf(String key, Bitmap value) {
            return value.getByteCount();
       }
    }

二、解析源码

构造方法:

    /**
     * @param maxSize 对于不覆盖sizeOf的缓存,这是缓存中的最大条目数。
     * 对于所有其他缓存,这是此缓存中条目大小的最大和。
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap(0, 0.75f, true);
    }

使用了LinkHashMap作为实际缓存的容器,其构造方法的第三个参数是accessOrder,设置为true表示按照访问顺序排序。

public void resize(int maxSize)
设置缓存的大小

LruCache#get
返回key的值(如果它存在于缓存中或可以由create创建)。如果返回值,则将其移到队列的开头。如果未缓存值并且无法创建,则返回null。

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

        /*
         * 尝试创建一个值。这可能会花费很长时间,并且当create()返回时,map可能会有所不同。
         * 如果在create()工作时将冲突值添加到map中,则将该值保留在map中并释放创建的值。
         */

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

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

            if (mapValue != null) {
                // 发生了冲突,因此撤消前面插入的创建的值
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

LruCache#put
为key缓存value。该值将插入到队列的开头。

    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);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);
        return previous;
    }

LruCache#trimToSize
删除最旧的条目,直到剩余条目总数等于或小于请求的大小。

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

                //调用了LinkedHashMap#eldest来获取最近最少使用的条目
                Map.Entry toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

public final V remove(K key)
删除key条目(如果存在)

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
当有条目被移除或调用put导致值被替换时会调用该方法,默认是空实现
evicted参数为true表示是缓存满了为了腾出空间而移除的,其它情况为false

protected V create(K key)
当key未命中缓存时调用该方法生成对应key的值,默认返回null

protected int sizeOf(K key, V value)
以用户定义的单位返回key和value的条目大小。默认实现返回1,因此size是条目数,max size是最大条目数。

public final void evictAll()
清除缓存,对每个已删除条目调用entryRemoved

三、LinkedHashMap

最近最少使用的顺序是由LinkedHashMap来确保的,使用了一个双向链表

    /**
     * 双向链表的头(最老)
     */
    transient LinkedHashMapEntry head;

    /**
     * 双链表的末尾(最小)
     */
    transient LinkedHashMapEntry tail;

    /**
     * 此LinkedHashMap的迭代排序方法:true 表示访问顺序, false 表示插入顺序。
     * @serial
     */
    final boolean accessOrder;

前面提到LruCache初始化LinkedHashMap时accessOrder为true,按访问顺序排序。
accessOrder为true的情况下,双向链表的头是最近最少使用的条目,尾是最近最多使用的条目。

我们看看这个accessOrder在哪里起到了作用

    public V get(Object key) {
        Node e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

在LinkedHashMap#get方法中,accessOrder为true就调用LinkedHashMap#afterNodeAccess

    void afterNodeAccess(Node e) { // 将节点移到最后
        LinkedHashMapEntry last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMapEntry p =
                (LinkedHashMapEntry)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;
        }
    }

在put的过程中,如果HashMap中不存在对应键的条目,就用了newNode来生成新的条目,LinkedHashMap重写了该方法,把新条目连接到链表末尾

    Node newNode(int hash, K key, V value, Node e) {
        LinkedHashMapEntry p =
            new LinkedHashMapEntry(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

    // 连接到链表末尾
    private void linkNodeLast(LinkedHashMapEntry p) {
        LinkedHashMapEntry last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

前面提到移除条目时调用LinkedHashMap#eldest获取最近最少使用条目,该方法直接返回了head

    public Map.Entry eldest() {
        return head;
    }

四、实现一个简易LruCache

import java.util.LinkedHashMap;
import java.util.Map;

public class LruCache extends LinkedHashMap{
    int cacheSize;
    public LruCache(int cacheSize){
        super((int) Math.ceil(cacheSize / 0.75),0.75f,true);
        this.cacheSize = cacheSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size()>cacheSize;
    }
}
  1. 继承了LinkedHashMap,调用父类构造方法,设置双向链表按访问顺序排序。

  2. 重写 LinkedHashMap#removeEldestEntry 方法,当 size 大于指定缓存容量时,就返回了true,LinkedHashMap就会删掉最旧的元素。

你可能感兴趣的:(LruCache了解一下)