Android LruCacha 源码分析

LRU 算法,中文叫 近期最少使用 算法。这个算法的思想是当容量已经满的情况下,我们再次向容器里面存放东西的时候,我们需要先删除之前使用最少的元素,因为这个元素在新元素插入之间一使用的最少,我们认为在后面的过程中,这个元素依然是使用次数最少的,当容积不够的时候,我们当然要删除后面过程中使用次数最少的,腾出空间给新的元素。

Android 里面实现了这种算法,名字叫 LruCache 。下面我们从源码(android sdk 23)的角度来解析这个类

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map; // 最重要的一个变量,上面所说的原理就是利用这个 LinkedHashMap 实现的

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size; // 已经缓存的元素个数
    private int maxSize; // 缓存大小

    private int putCount; // 往里面添加元素计数器
    private int createCount;// key 为 null 的情况下,默认生成对象的个数
    private int evictionCount; // 当加入的元素超过 maxSize 后,调整大小的次数
    private int hitCount; // 命中的次数
    private int missCount; // 没有命中的次数

    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    // 省略其他代码
}

我们先来看看 put 方法是如何工作的

public final V put(K key, V value) {
    // 如果 key 或 value 为 null 那么抛出异常,说明不接受 null
    if (key == null || value == null) {
        throw new NullPointerException("key == null || value == null");
    }

    V previous; // 记录 key 的旧值
    synchronized (this) {
        putCount++; // 插入计数器计数
        size += safeSizeOf(key, value); // 修正 size
        previous = map.put(key, value); // 取出 LinkedHashMap 里面的旧值
        if (previous != null) { // 之前的确保存过
            size -= safeSizeOf(key, previous); // 修正 size
        }
    }

    if (previous != null) { // 如果之前保存过,那么删掉这个节点,但是看源码发现,entryRemoved 是空
        entryRemoved(false, key, previous, value);
    }
    // 调整大小,也是最重要的方法之一
    trimToSize(maxSize);
    // 返回之前插入的值
    return previous;
}

刚才提到了 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!");
            }
            // size(缓存的元素个数) 小于 maxSize(容量) 那么跳出循环
            if (size <= maxSize) {
                break;
            }
            // eldest() 取 LinkedHashMap 里面最年老的元素
            Map.Entry<K, V> toEvict = map.eldest();
            if (toEvict == null) { // 为空说明 LinkedHashMap 里面没有元素了
                break;
            }

            key = toEvict.getKey();
            value = toEvict.getValue();
            // 删掉 最近最少使用的
            map.remove(key);
            size -= safeSizeOf(key, value); // 修正 size
            evictionCount++; // 缩减计数器
        }
        // 删除元素,entryRemoved 是一个空方法
        entryRemoved(true, key, value, null);
    }
}

trimToSize() 方法里面 LinkedHashMap.eldest() 方法很重要,LinkedHashMap.eldest() 的作用就是取出最年老,即最近最少使用的元素。如何取到最近最少使用的元素就是 LinkedHashMap 里面的东西了。我们接下来看看 LinkedHashMap 的源码

public class LinkedHashMap<K, V> extends HashMap<K, V> {

    /**
     * A dummy entry in the circular linked list of entries in the map.
     * The first real entry is header.nxt, and the last is header.prv.
     * If the map is empty, header.nxt == header && header.prv == header.
     */
    transient LinkedEntry<K, V> header;

    /**
     * True if access ordered, false if insertion ordered.
     */
    private final boolean accessOrder;

    /**
     * LinkedEntry adds nxt/prv double-links to plain HashMapEntry.
     */
    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;
        }

        /** Create a normal entry */
        LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
                    LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
            super(key, value, hash, next);
            this.nxt = nxt;
            this.prv = prv;
        }
    }

    // 省略其他代码
}

首先,有一个 LinkedEntry 内部类,继承自 HashMapEntry , 里面有 2 个引用分别指向之前和之后的元素,说明这个是一个双向链表结构。 LinkedEntry<K, V> header 这个变量就是双向链表的表头了。 accessOrder 这个的意思是是否需要排序,这个在后面会用到。我们先看看 eldest() 方法。

public Entry<K, V> eldest() {
    LinkedEntry<K, V> eldest = header.nxt;
    return eldest != header ? eldest : null;
}

方法很简单,就是取出 header 的 nxt 元素。那为什么 header 的 nxt 袁术就是最近最少使用的元素呢?这个是因为在 putget 过程中,如果 accessOrder 设置为 true 的话,会把对应 key 的 LinkedEntry 节点放到队列的尾部。下面我们通过 get 源码分析下这个过程。

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.
     */
    // 如果 key 为空,做key 为空的特殊处理
    if (key == null) {
        HashMapEntry<K, V> e = entryForNullKey;
        if (e == null)
            return null;
        if (accessOrder)
            makeTail((LinkedEntry<K, V>) e);
        return e.value;
    }
    // 计算 hash 值
    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    // 遍历,找到 key 对应的元素节点
    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) // accessOrder 如果为 true ,那么就执行将元素节点放到队列尾部的操作
                makeTail((LinkedEntry<K, V>) e);
            return e.value;
        }
    }
    // 如果这个 key 之前没有 put 过,那么就返回 null.
    return null;
}

看代码发现, accessOrdertrue 的时候,我们才会去做将元素节点放到队列的操作,而在 LruCache 里面,LinkedHashMap 的这个值是 true 的。整个的 LruCache 分析就到这里为止。
最后我们看下 makeTail 的源码

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

    // Relink e as tail 将 e 链接到队列尾部。所以队列头部就是最早put进去但是一直没使用的
    // 也就是所谓的 "最近最少使用"
    LinkedEntry<K, V> header = this.header;
    LinkedEntry<K, V> oldTail = header.prv;
    e.nxt = header;
    e.prv = oldTail;
    oldTail.nxt = header.prv = e;
    modCount++;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值