LruCache原理解析

355 阅读2分钟

简介

  • key-value结构。

  • 一个持有有限数量元素强引用的缓存机制。

  • 每次访问元素之后把它移动到序列的头部。cache已满的时候添加元素,序列尾部的元素就会被删除(释放引用)并可被GC。这就是最近最少使用的特性。LruCache保留频繁使用的元素,淘汰非频繁使用的元素。

  • 如果被缓存的元素持有需要被显式释放的资源,那么覆写entryRemoved函数去实现,因为删除只是代表释放引用,保存的数据可能需要a.release()去进行自己的释放。

  • 如果有这样的需求:get的时候没有即使命中元素也希望有元素返回,那么可以覆写create函数实现。

  • cache的size默认是按照key-value的数量决定的。如果想要按照内存大小去决定size,可以通过覆写sizeOf函数去实现。例如缓存4MB内存的bitmap:

    int cacheSize = 4 * 1024 * 1024; // 4MiB
    LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
            protected int sizeOf(String key, Bitmap value) {
               return value.getByteCount();
           }
       }}
    
  • 线程安全。

  • get的时候key不能为空,在put的时候key和value都不能为空。如果get,put或者remove返回空,那么很明显:该key不在缓存中。

  • LruCache中封装了LinkedHashMap,LruCache加上了控制缓存大小的逻辑,实际上是通过LinkedHashMap保存数据的。

基本原理

LruCache中有个LinkedHashMap类型的成员变量map,在LruCache的构造函数中将会初始化map变量。map=new LinkedHashMap(0,0.75f,true),实例化了一个以访问顺序来排序元素的LinkedHashMap对象。LruCache只是加上了控制缓存大小的逻辑,实际上数据是由LinkedHashMap保存的。

最近最少使用的特性是如何实现的?

由LinkedHashMap保证此特性,在实例化LinkedHashMap时指定accessOrder参数为true。调用LinkedHashMap的put或者get函数都会把该元素插入(或者移动)到序列的尾部。LruCache每次put的时候会检查当前的大小是否超过了限制,是的话就会调用trimToSize去把最近最少使用的元素淘汰掉。

trimToSize(int maxSize)

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) {//1.
                    break;
                }

                // BEGIN LAYOUTLIB CHANGE
                // get the last item in the linked list.
                // This is not efficient, the goal here is to minimize the changes
                // compared to the platform version.
                Map.Entry<K, V> toEvict = null;
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }
                // END LAYOUTLIB CHANGE

                if (toEvict == null) {//2.
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

trimToSize是个死循环,以下两种情况会退出循环:

  1. 当前的容量size<maxSize;

  2. LruCache中没有元素。

这个函数是实现LruCache元素数量限制的关键。它会把LinkedHashMap的尾部数据,即不“活跃”的数据,remove掉,从而来实现元素数量限制的特性。