LruCache是基于Least Recently Used(最近最少使用)算法实现的一个线程安全的数据缓存类,当超出设定的缓存容量时,优先淘汰最近最少使用的数据
LruCache的LRU缓存策略是利用LinkedHashMap来实现的,并通过封装get/put等相关方法来实现控制缓存大小以及淘汰元素,但不支持为null的key和value
因此先学习LinkedHashMap 原理才能更有助于理解本文
创建LruCache前先要定义缓存大小,然后在构造函数中传入缓存大小,并重写sizeOf方法计算value的大小
使用LruCache缓存Bitmap的典型使用方法如下:
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取可用内存的最大值,超出这个值会抛出OutOfMemory
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存的1/8作为缓存大小,以KB为单位
int cacheSize = maxMemory / 8;
// LruCache通过构造函数传入缓存值
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写sizeOf方法来计算图片大小
return bitmap.getAllocationByteCount() / 1024;
}
};
}
public void addBitmapToCache(String key, Bitmap bitmap) {
mMemoryCache.put(key, bitmap);
}
public Bitmap getBitmapFromCache(String key) {
return mMemoryCache.get(key);
}
LruCache只有一个构造函数,传入的参数就是LruCache的缓存大小,然后创建一个基于操作顺序排序的LinkedHashMap
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
// 设定缓存大小
this.maxSize = maxSize;
// LinkedHashMap accessOrder参数传递true
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
默认sizeOf函数返回1表示每个value大小是1
protected int sizeOf(K key, V value) {
return 1;
}
如有特殊需求比如缓存Bitmap则需要重写sizeOf方法,并返回bitmap占用的内存大小
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写sizeOf方法来计算图片大小
return bitmap.getAllocationByteCount() / 1024;
}
};
put方法首先会做判空处理,因此不能保存空的键值对,通过加锁实现线程安全,每次put数据后都会计算容量
public final V put(K key, V value) {
// 不支持为null的key和value
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
// 加锁保证线程安全
synchronized (this) {
putCount++;
// 计算键值对的大小后累加
size += safeSizeOf(key, value);
// 保存键值对,返回被替换的值,若不是替换则返回null
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方法会不停循环删除最不常用的数据直到不超过最大容量,最不常用的数据位于双链表的首位
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);
}
}
get方法同样首先会对key判空,然后通过key获取value,如果重写过create方法的话就会将该方法创建的value保存,但此时可能会有其他线程调用put方法先插入value,因此会做撤回处理
public final V get(@NonNull K key) {
// key判空
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
// 通过key查询value
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
// 不为空则返回value
return mapValue;
}
missCount++;
}
// 为空则调用create方法,默认返回null,可重写
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
// 保存通过create方法创建的value
mapValue = map.put(key, createdValue);
// 判断是否有其他线程提前插入了value
if (mapValue != null) {
// There was a conflict so undo that last put
// 有其他线程已插入value,将其他线程的value替换create方法的value
map.put(key, mapValue);
} else {
// 无其他线程插入value,计算键值对的大小后累加
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
remove方法比较简单,就是调用LinkedHashMap的remove方法,然后计算占用内存大小
public final V remove(@NonNull 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;
}