LruCache 源码分析

Posted yinhuanxu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LruCache 源码分析相关的知识,希望对你有一定的参考价值。

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

LruCache 的底层实现是 LinkedHashMap 。

先看到 LruCache 的构造方法:

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

配置了缓存的最大值,然后创建了一个 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<K, V> 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 的)

全文完。

以上是关于LruCache 源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Picasso源码解析之Lrucache算法源码解析

Java源码分析Android-LruCache源码分析

Java源码分析Android-LruCache源码分析

Android LruCache 源码分析

Android LruCache 源码分析

LruCache源码浅析