一句话解释:LruCache(least recently used cache)最近最少使用缓存。
前面,咱们一块儿学习了LinkedHashMap数据结构,那么LruCache就是LinkedHashMap的最佳实践,童鞋们能够查看个人博客线性表数据结构解读(六)链式哈希表结构-LinkedHashMap学习一下。
在平常开发中,咱们常常会使用一种内存缓存技术,即软引用或弱引用 (SoftReference or WeakReference)。可是如今已经再也不推荐使用这种方式了,由于从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得再也不可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,于是没法用一种可预见的方式将其释放,这就有潜在的风险形成应用程序的内存溢出并崩溃。
而谷歌大概从SDK21开始,提供LruCache这个工具类(此类在android-support-v4的包中提供) ,用于做为实现内存缓存技术的解决方案。这个类很是适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,而且把最近最少使用的对象在缓存值达到预设定值以前从内存中移除。java
源码解读
OK老规矩,我先带你们一块儿研读下LruCache的源码,咱们重点看下get、put、Remove等方法,其实原理就是LinkedHashMap的机制。android
public class LruCache<K, V> { private final LinkedHashMap<K, V> map;// 声明一个LinkedHashMap private int size;// 已经存储的数量大小 private int maxSize;// 规定的最大存储空间 private int putCount;// put的次数 private int createCount;// create的次数 private int evictionCount;// 回首的次数 private int hitCount;// 命中的次数 private int missCount;// 丢失的次数 /** * 指定最大内存的LruCache构造方法 * @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) {// 官方推荐maxSize通常声明为手机内存的1/8 if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); } /** * Sets the size of the cache. * @param maxSize The new maximum size. */ public void resize(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } synchronized (this) { this.maxSize = maxSize; } trimToSize(maxSize); } /** * 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) { mapValue = map.get(key); 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) { createCount++; mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } } /** * 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; } /** * 移除最老的元素,直到剩余元素数量等于或小于请求所需的大小 * 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) { 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); } } /** * 移除已存在的元素实体 * Removes the entry for {@code key} if it exists. * @return the previous value mapped by {@code key}. */ 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; } …… }
上面的关于LruCache初始化分配缓存大小有多少,能够参考下面几个因素:算法
- 你的设备能够为每一个应用程序分配多大的内存?
- 设备屏幕上一次最多能显示多少张图片?有多少图片须要进行预加载,由于有可能很快也会显示在屏幕上?
- 你的设备的屏幕大小和分辨率分别是多少?
- 图片的尺寸和大小,还有每张图片会占据多少内存空间?
- 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?若是有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不一样组的图片。
基本使用
Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。当cache已满的时候加入新的item时,在队列尾部的item会被回收。缓存
int cacheSize = 4 * 1024 * 1024; // 4MiB LruCache bitmapCache = new LruCache(cacheSize) { protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }
建立一个大小为4M的存储空间来进行图片的存储,存储按照队列的形式,后存储进来的和最新使用过的将会放在队列的最后,这样陈旧数据放在队列的开始,用于GC的回收。安全
synchronized (cache) { if (cache.get(key) == null) { cache.put(key, value); }}
这个方法也展现了怎样规范化的使用以及获取由LruCache保存的数据,因为这个类是线程安全的因此须要加上同步块来进行存放数据,经过get和put方式来进行数据的存取,这点跟Map是一致的,put时若是键相同则会进行数据的覆盖,可是有点须要注意这里key和value都不能为空,这里跟Map有点区别。
还必须注意必需要主动的释放资源,若是你cache的某个值须要明确释放,重写方法markdown
entryRemoved (boolean evicted, K key, V oldValue, V newValue)
若是资源是被系统回收的则evicted会返回TRUE,若是是由put,remove的方式替换回收的则evicted会返回FALSE,而后怎么知道是经过put仍是remove的,能够经过对newValue是否为空进行判断,若是为空则是put调用,而后将remove和系统回收时将资源置为空,就要本身去实现了。
若是key相对应的item丢掉啦,重写create().这简化了调用代码,即便丢失了也总会返回。默认cache大小是测量的item的数量,重写sizeof计算不一样item的大小。数据结构
参考连接:http://blog.csdn.net/linghu_java/article/details/8574102app