一、LruCache 简述和使用背景
核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用(我们平时正常使用的方式)存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
以前我们缓存图片通常使用的是对象的软引用或者弱引用,但是现在已经不推荐这种使用方式了,因为从Android 2.3(API Level 9)开始,垃圾回收器更倾向于回收这种持有软引用或弱引用的对象,这使得软引用和弱引用变得不再可靠。另外,Android 3.0(API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
当然你可能需要选择一个合适的缓存大小为你LruCache,不妨考虑下面的一些因素:你的设备可以为你的应用程序分配多大内存?设备屏幕一次最多能显示多少张图片,有多少图片需要预加载?设备的屏幕和分辨率多少(相同数量的图片在高分辨率下需要更大的缓存空间)?图片的尺寸和大小,每张图片会占据多少内存空间?有没有图片的访问频率教其他的高?
二、用法
以图片缓存为例
//获取系统分配给每个应用程序的最大内存,每个应用系统分配32M int maxMemory = (int) Runtime.getRuntime().maxMemory(); int mCacheSize = maxMemory / 8; //给LruCache分配1/8 4M mMemoryCache = new LruCache<String, Bitmap>(mCacheSize){ //必须重写此方法,计算每张缓存图片的大小 @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } };
三、LruCache的实现原理
LruCache核心就是维护一个缓存列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。这个队列就由LinkedHashMap来维护。
而LinkedHashMap是由数组+双向链表的数据结构来实现的。其中双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的<key,value>对按照一定顺序排列起来。
//accessOrder设置为true则为访问顺序,为false,则为插入顺序 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
//以具体例子解释: 当设置为true时 public void printMap() { LinkedHashMap<Integer, Integer> hashMap = new LinkedHashMap<>(0, 0.75f, true); hashMap.put(0, 0); hashMap.put(1, 1); hashMap.put(2, 2); hashMap.put(3, 3); hashMap.put(4, 4); hashMap.get(1); hashMap.get(2); for (hashMap.Entry<Integer, Integer> entry : hashMap.entrySet()) { Log.d("yang",entry.getKey() + ":" + entry.getValue()); } }
输出结果:
0:0 3:3 4:4 1:1 2:2
即最近访问的最后输出,可见LinkedHashMap这种数据结构正好满足的LRU缓存算法的思想。
四、源码分析
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); }
/** * 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); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, value); } //关键方法,调整缓存大小。超过最大缓存就删除较早的缓存对象 trimToSize(maxSize); return previous; }
trimTosize 方法:
/** * Remove the eldest entries until the total of remaining entries is at or * below the requested size. * * @param maxSize the maximum size of the cache before returning. May be -1 * to evict even 0-sized elements. */ public void trimToSize(int maxSize) { //不断循环,删除近期最少访问的对象,直到小于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.eldest()获得最近最少使用的对象 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); } }