情景学习Android中的LruCache

LruCache类是基于LRU算法的内存缓存管理类,在v4包下有一个,Android3.1及以上在android.util包下也有一个,注意两个的移除策略是不一样的。

一、简单使用

public class BitmapMemoryCache {

    private static final int maxSize = (int) (Runtime.getRuntime().maxMemory() / 6);

    private static final LruCache<String, Bitmap> sBitmapCache = new android.support.v4.util.LruCache<String, Bitmap>(maxSize){

        protected int sizeOf(String key, Bitmap value) {
            return sizeOfBitmap(value);
        }
    };

    public Bitmap put(String key, Bitmap bitmap) {
        if(TextUtils.isEmpty(key) || bitmap == null) {
            return null;
        }
        return sBitmapCache.put(key, bitmap);
    }

    public Bitmap get(String key) {
        if(TextUtils.isEmpty(key)) {
            return null; 
        }
        return sBitmapCache.get(key);
    }

    @SuppressLint("NewApi")
    public static int sizeOfBitmap(Bitmap bitmap) {
        if(bitmap == null) {
            return 0;
        }
        //API 19及以上版本
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            return bitmap.getAllocationByteCount();
        //API 12 ~ API 18
        } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
            return bitmap.getByteCount();
        //API 12以下
        } else {
            return bitmap.getRowBytes() * bitmap.getHeight();
        }
    }
}

A:上述代码有问题吗?有的话有哪些?

B:sBitmapCache是类变量而不是成员变量,不管我new了多少个BitmapMemoryCache对象然后进行put操作,其实put的都是同一个sBitmapCache,这与我们直觉是相违背的。

A:怎么改进?

B:可以考虑把sBitmapCache声明称非static的。

A:那假如现在我new了100个BitmapMemoryCache对象,也就是有100个sBitmapCache缓存池,按照代码中一个sBitmapCache的缓存池大小是虚拟机最大堆大小的1/6,显然100*1/6大大超过了虚拟机的最大堆大小,这样很容易导致OOM吧?

B:哦,那还是把sBitmapCache声明成static吧,所有缓存统一管理,因为虚拟机堆是共用,然后把BitmapMemoryCache做成单例就行。

A:这样做可以,不过把BitmapMemoryCache做成单例后有一个问题,那个单例对象一直持有sBitmapCache引用,而sBitmapCache持有许多Bitmap的引用,因此在不用的时候要及时解除引用好让垃圾回收器回收掉,否则会使一部分堆内存一直被占用。

A:上面的代码还有其它问题吗?有没有可能会发生OOM?

B:已经指定缓存池大小为最大虚拟机堆大小的1/6了,因此缓存池最多就占用1/6的最大虚拟机堆,应该不会发生OOM吧。

A:那如果其它地方占用的堆大小超过5/6呢?

B:对哦,sBitmapCache最多允许放1/6的数据,而实际上如果可用堆已经少于最大堆的1/6了,这时再put就有可能发生OOM。

A:那需要怎么改进?

B:在put方法里,每次要将缓存添加到sBitmapCache时,先判断一下可用堆大小还有多少,如果已经小于一个临界值那么我就不缓存该Bitmap,或者按照一定的规则(比如LRU算法)把缓存池里面的某些Bitmap移除掉再添加当前Bitmap进去。

二、深入理解LruCache实现原理

A:LruCache是怎么实现LRU算法的?

B:其内部使用的是一个LinkedHashMap保存缓存数据,而LinkedHashMap其实已经实现了LRU算法,因此LruCache其实是使用了LinkedHashMap的实现。

A:能否具体说一下LinkedHashMap的LRU算法是怎么实现的。

B:LinkedHashMap默认情况下并没有开启LRU功能,因为是使用双向循环链表实现,因此其迭代顺序就是添加顺序,但我们可以在其构造函数指定其开启LRU功能,具体到代码就是指定accessOrder为true,然后每次访问该LinkedHashMap时,它会把该节点移到链表的尾部,这样越靠近链头就是越少访问的数据了,以后超出最大容量了直接从链头开始删起就行。

A:你说的是v4包下的LruCache吧,其实在Android3.1开始,android.util包下也有一个LruCache,它们是完全一样的吗?有没有什么区别?

B:v4包下的LruCache才是真是按LRU算法移除掉元素的,也就是从LinkedHashMap的链头开始移除,而android.util包下的是从LinkedHashMap的链尾开始移除的,也就是移除最经常使用的元素,这个区别体现在它们的trimToSize方法的实现不同,而它们的其它所有代码及逻辑都是相同的。

A:LruCache是线程安全的吗?

B:是,在put和get操作LruCache都进行了加锁操作(使用this作为锁对象),因此是线程安全的。

A:你觉得加锁方式有改进的地方?

B:get操作并不会修改数据,可以使用Java中ReadWriteLock读写锁实现锁机制,这样效率会高一点。

A:LruCache里面有一个叫create方法,它是做什么用的?

B:在get操作发现没有要获取的元素时,会调用该方法,子类可以重写该方法实现在获取不到元素时自行创建相应的元素,这时get返回的可能就是这个元素。create该方法默认实现返回null。

A:为什么说可能返回create方法返回的那个元素呢,为什么不是一定?

B:因为在调用create并对存放数据的map没有加锁,这时可能会有另外一个线程put一个相同key的元素进map,这时可以选择直接返回map中的值,也可以选择返回create返回的值,当然LruCache采用第一种方法实现。

A:最后在问一个,知道putCount,createCount,evictionCount,hitCount和missCount的含义吗?

B:putCount表示进行put的操作次数,createCount表示调用create的次数,evictionCount表示被移除的元素数,hitCount表示目中次数,missCount表示未命中次数。

转载请注明原文地址:http://blog.csdn.net/u012619640/article/details/50525464

你可能感兴趣的:(源码,android,LRU,缓存)