LruCache是最近最久未使用的缓存,是安卓系统里常见的缓存策略之一。
LruCache类里定义的属性如下
private final LinkedHashMap map; // 内存对象,哈希链表
private int size; // 目前使用的内存数
private int maxSize; // 最大内存数
private int putCount; // 添加到map队列的操作数
private int createCount; // 添加的空白内存的数量
private int evictionCount; // 被lru算法删除(而不是用户手动调用删除)的内存数量
private int hitCount; // 根据键找值时找到的次数
private int missCount; // 找不到值的次数
原来就是封装了一个哈希链表,然后记录各种数量。于是我顺着构造方法、增加、查找、删除、清空以及其他方法的顺序进行源码阅读
构造方法代码如下,传入了一个maxSize参数
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap(0, 0.75f, true);
}
保存最大内存数量,然后实例化map,但是map初始长度是0,增长因子是0.75(也就是当添加一个元素进去时,发现当前链表长度达到或超过链表最大长度的75%时,会先扩容到原来的两倍,再添加新元素),第三个参数表示映射链表的排序方式,true表示按照访问顺序排列,false表示按照插入顺序排序,这里要实现最近最久未使用,自然要按照访问顺序排列映射链表,把不常用的放到链表头部,常用的放到链表尾部。
给缓存里添加元素的方法代码如下,实际是对映射链表的操作
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++; // 移入数量+1
size += safeSizeOf(key, value); // size++
previous = map.put(key, value); // 把新的键值对插入,返回键对应的老值
if (previous != null) {
size -= safeSizeOf(key, previous); // 如果以前有值,size不应该变化
}
}
if (previous != null) { // 回调,空实现
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize); // 保证size不大于maxSize
return previous;
}
这个方法前面的都比较容易理解,而最后的trimToSize()方法则是用来剔除链表首部那些不常用的元素的,剔除的幅度是保证当前映射链表长度不大于传入的参数。我们看一下trimToSize()方法
public 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!");
}
if (size <= maxSize) { // 当前的长度不超过传入的参数,退出循环,结束方法
break;
}
Map.Entry toEvict = map.eldest(); // 直接返回头结点
if (toEvict == null) {
break; // 头结点是空,链表就是空,退出循环,结束方法
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value); // size--
evictionCount++; // 移除数量++
}
entryRemoved(true, key, value, null); // 空实现
}
}
获取元素的方法是get()方法,代码如下
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++; // 否则,丢失数++,进入下面的处理
}
V createdValue = create(key); // 默认返回null
if (createdValue == null) {
return null; // 返回null
}
// 如果某个类覆写了create()方法,让它返回不是null
synchronized (this) {
createCount++; // 创造数+1
mapValue = map.put(key, createdValue); // 先尝试把新值插进去,获得老值
// 如果老值不为空,就不采用新值,还把老值插回去,否则size++
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue); // size++
}
}
if (mapValue != null) { // 老值不是null,返回
entryRemoved(false, key, createdValue, mapValue); // 空实现
return mapValue;
} else { // 否则就是插入了新值,返回新值
trimToSize(maxSize); // 同时执行lru算法,清除头结点
return createdValue;
}
}
删除方法是remove(),代码如下
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); // size--
}
}
if (previous != null) {
entryRemoved(false, key, previous, null); // 空实现
}
return previous;
}
清空方法是evictAll()方法,代码如下
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
给trimToSize()传入的参数是-1,那么trimToSize()方法会从链表首位开始清除,直到映射链表的大小是0为止,当然就清空了
resize()方法,代码如下
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
其实就是改变一下maxSize,然后调用trimToSize()方法执行lru以适应新的容量
snapShot()方法,代码如下
public synchronized final Map snapshot() {
return new LinkedHashMap(map);
}
就是获取一个map的副本出去
安卓里的LruCache类就是这样,内部核心是维护了一个映射链表,以及实现了一个trimToSize()方法,这个方法也是lru的实现。
以上源码来自Android8.0,sdk26