一、LruCache 是什么玩意?
官方介绍:
缓存有限数量强引用。每次访问的值,它都会被移到队列的头部。当添加一个值到缓存队列是,该队列末尾的值将被删除,并且可能会被回收。
坊间总结:
LruCache用来缓存最近期间最少使用的算法。核心思想就是,优先清除那些最近最少使用的对象
二、LruCache 干啥用哩?
优先清除那些最近最少使用的对象
三、LruCache 咋用呢?
定义图片缓存工具类 BitmapLRUtils
public class BitmapLRUtils extends LruCache<String, Bitmap> {
private static int mDefaultMaxSize = 10 * 1024 * 1024;// 默认缓存图片大小 10M
/**
* @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.
*/
private BitmapLRUtils(int maxSize) {
super(maxSize);
}
private static BitmapLRUtils sLrUtils;
public static BitmapLRUtils getInstance() {
if (sLrUtils == null) {
sLrUtils = new BitmapLRUtils(mDefaultMaxSize);
}
return sLrUtils;
}
@Override
public void resize(int maxSize) {
// 调整缓存空间最大值
super.resize(maxSize);
}
@Override
public void trimToSize(int maxSize) {
// 裁剪空间
super.trimToSize(maxSize);
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
// 同一个Key 对应的值被替换修改时
super.entryRemoved(evicted, key, oldValue, newValue);
}
@Override
protected Bitmap create(String key) {
// 在根据Key 获得缓存失败后调用,不重新此方法 默认返回 null
// return super.create(key);
// 开发者 可以设置一个默认值
return BitmapFactory.decodeFile("aa/bb/cc.png");
}
@Override
protected int sizeOf(String key, Bitmap value) {
// 默认size大小为1
// return super.sizeOf(key, value);
// 用户自己计算 保存 Value所需要的空间
return value.getByteCount();
}
使用:
BitmapLRUtils.getInstance().put("http://xxxx.jpg", BitmapFactory.decodeFile(""));
BitmapLRUtils.getInstance().get("http://xxxx.jpg");
四、LruCache 源码分析?
构造方法 LruCache(int maxSize)
public LruCache(int maxSize) {
// 设置最大缓存大小
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
// 使用LinkedHashMap 保存对象
// 为什么使用 LinkedHashMap???
// 通过key-value 选择map 方便
// Lru算法增删多,查找少 使用链表结构 Link .
// 其他 ...
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
get(V v):获取数据的方法
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
// 同步代码块 ,多线程 安全
synchronized (this) {
// 从map中取值
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.
*/
// 下面一坨干嘛用的呢?
// create() 默认是返回null ,
// 其 被 protected 修饰 说明给子类重写 创建一个默认的值
V createdValue = create(key);
// 默认是返回null ,
if (createdValue == null) {
// 通过key,获取vlue为空
return null;
}
synchronized (this) {
createCount++;
// 保存创建的,发现key 已有映射的值???
// 那为什么 map.get(key) == null 呢???
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// 有一个冲突,所以撤销最后一个put
map.put(key, mapValue);
} else {
// 添加 vaule的大小
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
// mapValue 不为空,则不保存 createdValue
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
// 计算裁剪空间大小
trimToSize(maxSize);
return createdValue;
}
}
// 自定义默认值
protected V create(K key) {
return null;
}
put(K k,V v):保存数据
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 不为null 时 说明key之前有映射的对象
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(int maxSize) 计算空间大小
private void trimToSize(int maxSize) {
// 不断的循环 直到 达到size<=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!");
}
// size 没有超出最大缓存大小 终止!!!
if (size <= maxSize) {
break;
}
// BEGIN LAYOUTLIB CHANGE
// get the last item in the linked list.
// This is not efficient, the goal here is to minimize the changes
// compared to the platform version.
Map.Entry<K, V> toEvict = null;
// 获取链表中的最后一项。
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
// END LAYOUTLIB CHANGE
// 链表为空
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
// 移除value
map.remove(key);
// 减少size
size -= safeSizeOf(key, value);
evictionCount++;
}
// 移除 value
entryRemoved(true, key, value, null);
}
}
五、LruCache 总结?
LruCache
1、通过Linkhashmap做为容器通过Key-value 进行 保存、获取、移除操作
2、如何保证被移除的是最后一个呢?
Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
3、如何保证被移除的是最近最少使用的呢?
翻遍源码 也没发现 ,有知道的大佬请指教
最终对比了下:
trimToSize()方法在不同的版本是有不同的实现的。
// 在android-28 SDK 中,只是获得map集合中的最后一个元素,进行移除
Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
// 而在其他版本中:
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
4、通过Value对象的大小加减到size变量上,再与maxSize 大小比较。判断移除条件是否成立
参考文章:
LruCache缓存使用