您的位置:首页 > 移动开发 > Android开发

Android缓存-LruCache分析

2015-08-17 14:52 399 查看
缓存虽然会占据一定的应用内存或者磁盘空间,却允许你以更快的速度获得你需要的对象。因此缓存避免了重复加载,可以提高应用的响应速度。Android中提供了LruCache类来支持缓存。这篇文章着重于对LruCache源码的分析。

类的介绍

LruCache持有一定数量对象的引用来避免这些对象被回收。当一个对象被访问时,将会被移到缓存队列的头部;当一个对象添加到已经满的队列时,队列的尾部对象(最不常访问)的对象将被踢出队列,意味着允许被垃圾回收器所回收。

如果在缓存队列中存在一些需要显性释放资源的对象时,需要重写entryRemoved方法。

如果缓存miss时,需要根据相应的key来产生对应的value时,需要重写create方法。

默认地,cache大小为缓存队列中的元素个数。然而,在实际运用中,cache大小与缓存队列中的对象大小有关。因此,需要重写sizeOf来改变这一点。

此类是线程安全的,通过synchronized关键字来限制put、get、remove操作。

LruCache不允许key或者value为空,否则将抛出异常-NullPointerException(“key == null”)。

变量介绍

[code]    private final LinkedHashMap<K, V> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;
    private int maxSize;

    private int putCount;
    private int createCount;
    private int evictionCount;
    private int hitCount;
    private int missCount;


map为缓存队列,LinkedHashMap内的队列有两种维护顺序的方式,一种是根据插入的顺序,新插入的元素将在尾部;另外一种是根据访问的顺序,新访问的元素将在尾部。LruCache中的LinkedHashMap使用的是根据访问顺序来实现LRU(最近最少访问)的。

size不是缓存队列中元素的个数,而是缓存队列中元素的大小,例如一个Bitmap所占的字节数;maxSize是缓存的最大空间。

putCount记录了队列中添加元素的次数;有时,缓存miss时,需要根据相应的key来产生value,createCount负责统计次数;evictionCount则是记录了踢出队列元素的个数;hitCount和missCount分别表示缓存hit和miss的次数。

构造函数

[code]    /**
     * @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) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }


如果没有重写sizeOf方法,maxSize指的是队列中元素的最大数目;否则,maxSize指的是cache的最大空间。

this.map = new LinkedHashMap

trimToSize

[code]    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) {
                    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);
        }
    }


trimToSize(int maxSize)方法是用来限制缓存队列大小到maxSize的方法。从代码中可以看得出,如果现在的size大于maxSize的话,将移除最不常访问的。当参数传入-1时,表示移除所有元素。

get方法

[code]   /**
     * 如果存在或者能通过create方法生成的话,返回key对应的value
     * 如果有value返回的话,它将会被移动到缓存队列的尾部. 
     * 当缓存miss并且不能通过create方法生成时,返回null
     */
    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++;
        }

        /*
         * 尝试生成value,这可能会耗费一定的时间,在这段时间内,map可能有所变化,因此同一个key可能有
         * 冲突,如果冲突发生,我们将遗弃通过create生成的value
         */

        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // 有冲突,将mapValue重新存入
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            // 显性释放资源
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            // 由于put了createdValue,验证是否大小超过maxSize
            trimToSize(maxSize);
            return createdValue;
        }
    }


从代码中,我们可以知道:

如果缓存队列中存在此key的话,直接返回key相应的value;

如果不存在的话,会尝试通过create方法来创建此value。create方法前面已经说过,就是如果缓存miss时,需要根据相应的key来产生对应的value时,需要重写create方法。由于create方法可能消耗一定的时间,因此在create返回之前,map内可能已经有这个key了,这时就会发生冲突,因此我们会遗弃create产生的value。

put方法

[code]        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方法中,如果key或value为空,返回空指针异常。如果原本有key存在,需要显性的释放资源。添加成功后,需要调用trimToSize方法避免size超过最大大小。

remove方法

[code]    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;
    }


这个方法也比较简单,移除成功的话,需要释放资源。

其他方法

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


如果缓存踢出对象需要显性释放资源的话,需要重写这个方法。

evicted为true的话,说明是由于空间不足造成的;为false的话,说明是由于put或者remove造成的。

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


如果缓存miss的话,需要根据对应的key产生value的话,需要重写此方法。

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


返回某个元素的大小,在下面方法的基础上,添加了不为负数的判断。

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


返回某个元素的大小,默认返回的元素大小为元素的数目1,一般需要重写。

[code]     public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }


清空缓存队列。

到此为止。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: