Android LruCache初探

来源:互联网 发布:php jquery post json 编辑:程序博客网 时间:2024/04/27 13:47

在LinkedHashMap中,我们知道,LinkedHashMap为我们实现特定替换策略的Map Cache预留了接口,即以如下形式重写removeEldestEntry函数:

          private static final int MAX_ENTRIES = 100;               protected boolean removeEldestEntry(Map.Entry eldest) {             return size() > MAX_ENTRIES;          }
但是LinkedHashMap有一点不足在于其实现过程中没有考虑过并发访问的问题,即在多线程环境下对LinkedHashMap进行访问并不安全。

1.Andriod LruCache概述

Andriod开发这网站上给出如下描述:

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

If your cached values hold resources that need to be explicitly released, override entryRemoved(boolean, K, V, V).

If a cache miss should be computed on demand for the corresponding keys, override create(K). This simplifies the calling code, allowing it to assume a value will always be returned, even when there's a cache miss.

By default, the cache size is measured in the number of entries. Override sizeOf(K, V) to size the cache in different units. 

可以看出在Andriod LruCache中实现中还是给开发这留出了几个灵活的扩展接口,包括entryRemoved(boolean, K, V, V)用于对特定的Cache元素进行内存空间释放操作 , create(K) 用于在访问失效时,为特定的key生成键值的操作,当然这两个函数在源码实现中都是空。

2.底层数据结构支持

我最初以为Andriod LruCache的实现是继承了LinkedHashMap,并重写了removeEldestEntry函数,但是一看源码完全不是这么回事,所以首先看一下LruCache的内部数据域以及其构造函数:
        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;                           //Cache元素的个数上限private int putCount;private int createCount;private int evictionCount;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) {                //构造函数,初始化LinkedHashMapthrow new IllegalArgumentException("maxSize <= 0");}this.maxSize = maxSize;this.map = new LinkedHashMap<K, V>(0, 0.75f, true);    //注意参数,true代表这AccessOrder}
到这里你会想到,当我们对LruCache进行操作时,都是对其内部封装的LinkedHashMap进行操作。

3.put

put函数内部还是调用了LinkedHashMap的put操作,但是在操作过程中进行了加锁操作,保证同一时刻只能有一个线程进行数据插入操作。
/** * Caches {@code value} for {@code key}. The value is moved to the head of * the queue. * * @return the previous value mapped by {@code key}. */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);     //调用LinkedHashMap的put操作if (previous != null) {size -= safeSizeOf(key, previous);}}if (previous != null) {entryRemoved(false, key, previous, value);}trimToSize(maxSize);                         //缓存容量检测,以保证缓存数据量不超过最大容量return previous;}

4.get

/** * Returns the value for {@code key} if it exists in the cache or can be * created by {@code #create}. If a value was returned, it is moved to the * head of the queue. This returns null if a value is not cached and cannot * be created. */public final V get(K key) {if (key == null) {throw new NullPointerException("key == null");}V mapValue;synchronized (this) {                   //加锁的get操作mapValue = map.get(key);        //调用LinkedHashMap的get操作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) {                          //加锁处理,保证第一个被创建的value被缓存,从而保证了缓存数据的一致性createCount++;     mapValue = map.put(key, createdValue); //当前线程创建成功,进行put操作 if (mapValue != null) {// There was a conflict so undo that last putmap.put(key, mapValue);        //发现之前已经有线程完成创建操作了,保持原有的缓存value操作} else {size += safeSizeOf(key, createdValue);}}if (mapValue != null) {                         //执行到这里,证明一定发生了替换操作,并且当前线程是后替换操作entryRemoved(false, key, createdValue, mapValue);  //释放当前线程创建的valuereturn mapValue;} else {trimToSize(maxSize);                    //到这里证明当前线程完成了创建操作,并且当前线程创建的value被缓存return createdValue;                    //所以需要进行缓存容量检测}}

5.缓存容量控制trimToSize

/** * @param maxSize the maximum size of the cache before returning. May be -1 *     to evict even 0-sized elements. */private 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);  //本线程负责释放已存在元素的空间}}

5.小结

Android的LruCache的实现是对LinkedHashMap进行了一层封装,并在其中加入了支持多线程访问的线程锁操作,因此LruCache时支持并发访问的。



0 0
原创粉丝点击