LruCache缓存
学过计算机操作系统的都应该指定LRU(Least Recently Used),LRU的意思就是近期最少使用算法,它的核心思想就是会优先淘汰那些近期最少使用的缓存对象。
Android开发中经常要用到缓存,其中的三级缓存,主要就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LRU算法。Android应用缓存数据分有两种形式:
①数据缓存:主要用于保存业务的数据,如接口的返回数据
②图片缓存:主要用于保存应用程序的图片对象,图片在程序里使用比较频繁,而且比较占有网络资源。
1、LruCache
好在Android系统自Android 3.1 (Honeycomb MR1)开始就为我们提供了LruCache工具类,位于android.util包中,内部封装了实现了LRU算法,使用LinkedHashMap保存键Key和数据Value。一般用MD5来当做key,而不是直接用字符串。MD5生成参考MD5:Java String生成MD5
LruCache构造函数传入缓存区域上限maxSize。
/**
* @param maxSize 对于不重写sizeOf(K key,V value)方法的缓存来说,就是所缓存的最大条目数量。
* 对于重写了sizeOf(K key,V value)方法的缓存来说,就是实际所能缓存的最大数量。
*/
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);
}
2、LruCache中的方法
LruCache源码不到400行,可以很快的看明白其中的实现原理,甚至可以自己仿写一个类似的LruCache功能。不仅要会灵活的使用,还要懂得其中的思想。
2.1、int sizeOf(K key, V value)
该方法的默认实现返回每条缓存大小1
/**
* Returns the size of the entry for {@code key} and {@code value} in
* user-defined units. The default implementation returns 1 so that size
* is the number of entries and max size is the maximum number of entries.
*
* <p>An entry's size must not change while it is in the cache.
*/
protected int sizeOf(K key, V value) {
return 1;
}
一般情况下需要重写该方法,确定每个缓存的大小。这样的话sizeOf()方法才能返回每个具体缓存实际的大小,而不是缓存数量。也不一定必须重写该方法,看情况,我就在开发中没有重写该方法,因为用存储最大的条目来限制,而非总共分配的内存大小来限制。
2.2、size()
返回实际缓存数据的大小,如果没有重写sizeOf()方法的话,每条缓存数据大小都是“1”,缓存条目数量putCount和缓存大小size就是一样的。
public synchronized final int size() {
return size;
}
2.3、maxSize()
该方法返回缓存最大容量,也就是开始创建LruCache是传入的maxSize:
public synchronized final int maxSize() {
return maxSize;
}
2.4、put(K key, V value)
数据放入缓存,并且调用trimToSize()不断删除超出大小的元素,直到缓存小于最大值。
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数据进来都会使用safeSizeOf(key,value)计算本次缓存的数据大小并统计。
2.5、entryRemoved()
特殊情况下需要重写该方法,每当存储的条目被remove或者移除掉,需要执行比如释放资源的操作就可以重写该方法。比如Bitmap缓存,当缓存的Bitmap条目不再使用被移除的时候,可以重写该方法,是否Bitmap底层资源。
/**
* Called for entries that have been evicted or removed. This method is
* invoked when a value is evicted to make space, removed by a call to
* {@link #remove}, or replaced by a call to {@link #put}. The default
* implementation does nothing.
*/
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if( null != oldValue){
//释放Bitmap
oldValue.recycle();
}
}
2.6、private safeSizeOf(K key, V value)
为什么会有这个二次封装呢?我猜Android可能对重写sizeOf()方法的"蹩脚"程序员不放心,再次检验一下数据是否合法。否则内部会错乱。
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
3、简单使用
一般用LruCache缓存图片等资源,如下:
// 缓存最多4MiB的图片
int cacheSize = 4 * 1024 * 1024;
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize)
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
};
注意:必须重写sizeOf()方法计算每个缓存Bitmap的大小。否者的话会闹笑话,搞出OOM问题,此时maxSize意思是最多可以缓存4*1024*1024个Bitmap条目。
相关博客:
Bitmap的缓存(LruCache,DiskLruCache)
LruCache 的使用及原理
完全解析Andorid的缓存机制LruCache
什么是LRUCache 和 LRUCache 实现