LruCache 源码分析

我在参加笔试的时候,有一道题是设计一个 LruCache,当时由于不理解原理而没有写出来,现在看了几遍源码,记录下笔记理清思路。

LruCache 的底层实现是 LinkedHashMap 。

先看到 LruCache 的构造方法:

public LruCache(int maxSize) {
    if (maxSize <= 0) {
        throw new IllegalArgumentException("maxSize <= 0");
    }
    this.maxSize = maxSize;
    this.map = new LinkedHashMap(0, 0.75f, true);
}

配置了缓存的最大值,然后创建了一个 LinkedHashMap 对象,如果你了解 LinkedHashMap,会知道这个 Map 是可以实现 LRU 算法的。

而这里恰好开启了 LRU 算法,没错,LinkedHashMap 构造方法第三个参数传进 true 就是了。

我们来看 LruCache 的 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);//添加缓存
        if (previous != null) {
            size -= safeSizeOf(key, previous);
        }
    }

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

    trimToSize(maxSize);//整理缓存
    return previous;
}

可以看到,key 和 value 都不允许为 null。在 synchronized 代码块中,调用了 LinkedHashMap 的 put 方法将 key 和 value 传进,缓存起来。而且每一次调用 put 方法,都会调用 trimToSize 方法。

你应该和我一样关心 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;//缓存未满,跳出循环体
            }

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

看到这一行核心代码

Map.Entry<K, V> toEvict = map.eldest()

ps:Evict 是逐出的意思

可以看到这里调用了 LinkedHashMap 的 eldest 方法,

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

这个方法取出了 LinkedHashMap 的双向链表头节点的下一个节点。

补充一下LinkedHashMap:

LinkedHashMap 在开启 LRU 算法后,每次调用 put 方法和 get 方法,都会将(最近最常访问的)元素放在链接的结尾。也就是说,多次调用 LinkedHashMap 的 put 和 get 方法,位于链表前面的元素,就是最近最少使用的了,应该被移除。

而这里取出头节点的下一个节点,肯定是移除它的啦。这也是合理的。好,我们继续看到 LruCache 的 trimToSize 方法,之后就是调用了 HashMap 的 remove 方法来移除元素了。(LinkedHashMap 是继承自 HashMap 的)

全文完。

你可能感兴趣的:(Android开发)