05
2017
10

LruCache 源码解析

LruCache 作为AS的自带类,实现了一种内存缓存策略——最近使用算法,即在满足所拥有的内存限制的条件下,缓存近期使用的数据,而当数据大小超出限时时,移除最久未被使用的数据。LruCache常用如图片等的缓存。下面来看源码。

    private final LinkedHashMap<K, V> map; 

    private int size;  // 当前缓存大小
    private int maxSize; // 最大缓存大小

    private int putCount;  // 添加数据进缓存的次数
    private int createCount; // 根据需要自己创建创建缓存数据的次数
    private int evictionCount; // 移出缓存的次数
    private int hitCount;  // 命中,即找到对应数据的次数
    private int missCount; // 找不到对应数据的次数

以上是LruCache的成员变量,可以看到LruCache是用LinkedHashMap实现的最近使用缓存策略,该数据结构继承HashMap,在此基础之上,它有一个双向链表 (如果忘了LinkedHashMap,先补补哦)。

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

LruCache的构造方法,根据传入的最大内存限制,实例化LinkedHashMap

    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        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);
        }
    }

resize()方法能重新限定缓存大小,那这里会包含有一个问题,就是新的maxSize小于旧的maxSize,而此时的size大于新的maxSize , 为了避免这个问题的出现,在resize()方法里调用trimToSize()调整当前缓存。

trimToSize()函数有一个死循环,如果size小于maxSize,则此循环关闭。反之,移除map的一个最远key,使用函数safeSizeOf()更新size值和移除数量evictionCount值,重复此过程。

    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

    protected int sizeOf(K key, V value) {
        return 1;
    }

上面提到的更新size值的方法safeSizeOf() ,该方法调用sizeOf(),sizeOf()是我们在实例化LruCache时,需要覆盖的函数, 返回值是所要缓存的数据的大小。

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

紧接着是put()操作,put操作会将键值加入到map里,更新当前缓存size和加入次数的putCount值,返回原先的value值,即当前的key在此put之前已经对应了一个旧的value,当然也可能这个key还没有被使用。 所以size的更新可能需要更新两次。 紧接着还要调用trimToSize()调整内存的缓存数据,在加入新的value之后,可能超出了内存限制。

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

注意到在旧的value被移除后,调用了entryRemoved()方法。再默认情况下,次方法什么也不做,用来做为一个通知。也就是说,当新老数据交替时,我们可能会根据需要做一些事情,此时,就可以覆盖entryRemoved()方法。

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

        /* * Attempt to create a value. This may take a long time, and the map * may be different when create() returns. If a conflicting value was * added to the map while create() was working, we leave that value in * the map and release the created value. */

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

    protected V create(K key) {
        return null;
    }

get()的操作比较重要,也需要一定的理解,下面的说明会有点长

get()操作会从根据key从map里取出缓存的数据value,如果取到了,直接返回

当取不到的时候,会调用create()尝试创建一个value.。 那么在这里注意了,用的词是尝试。也就是说create()方法在默认情况什么也不做,返回null,而当get()里的create()返回null时候,也返回null。create()操作提供一种需要,就是在当前key还有被使用时,可能会需要根据这个key生成相应的value,此时,就需要覆盖create()完成此项工作,然后将value返回到get()。在这里,又衍生出了一个问题,即是LruCaChe是支持线程操作的,在create()的过程中(此过程还可能是耗时操作),map的结构可能已经改变,针对这一种情况,需要做相应的处理。

在①处将生成的createdValue加入到map中,mapValue 保存的是原来key对应的value。是的,没错,我们是因为根据key没有找到对应的value,才调用了create()生成了createdValue走到了这一步,怎么mapValue会有值呢?别忘了,LruCache是支持线程操作的,也就是其他的线程可能在create()的过程中,调用了put,利用了这个key,将value加入了map。 那么此时的key当然对应了一个值的。 当然,mapValue可能为null,因为在此之前key未被使用

在②处,mapValue保存了原来key对应的value,程序走到这里,说明别的线程使用了key,保存了对应的value。换而言之,不需要我们通过create()创建的createdValue了,但是key对应的value已经被我们更改为createdValue。那么,我们通过再次将mapValue加入到map中,就可以恢复原来的value值

在④处,因为不需要我们生成的createdValue,因此返回原来的mapValue,还使用了entryRemoved()进行了通知。

在③处,说明需要我们生成的createdValue,因此要更新当前内存size值

在⑤处,需要我们生成的createdValue,因此在加入这个createdValue后内存可能超限,因此要调整map

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

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

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

        return previous;
    }

remove操作根据key删除相应的key和value,返回value

public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

返回map

到这里,解析完毕。LruCache常用操作为put和get,以及在实例化时需要覆盖的sizeOf。get方法是需要仔细体会的地方

此博客供个人笔记使用,如有不足,欢迎指出,感激不尽

上一篇:将项目上传到github 下一篇:安卓开发-intent属性总结