LruCache 源码解析

来源:互联网 发布:网络最好的诈骗文章 编辑:程序博客网 时间:2024/05/17 23:34

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方法是需要仔细体会的地方

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

原创粉丝点击