LRUCache是Android中实现内存缓存相关的组件类,当缓存满时其使用最近最少使用策略来淘汰相关的元素,以控制缓存大小。本文主要基于LRUCache相关源码分析LRUCache的创建、缓存的添加、获取、删除流程。
LRUCache创建
LRUCache的创建可以直接看其构造函数
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);
}
其创建时需要设置最大的缓存大小,此外会创建一个LinkedHashMap来存储缓存对象的引用。
当我们使用LRUCache时一般如下:
int cacheSize = 4 * 1024 * 1024; // 4MiB
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap(cacheSize) {
、 protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
}
添加缓存
添加缓存调用LRUCahe的put(key,value)方法,put方法先通过sizeof函数计算当前待添加节点所占内存大小,然后将其添加到map中,重新计算当前缓存大小,如果旧节点非空,则调用entryRemove方法通知该旧节点已移除,以方便使用者做清理操作。最后再调用trimToSize()方法检查并移除已超过maxSize的最老的节点。
public final V put(K key, V value) {
//注意 key和value都不能为null
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;
}
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) {
break;
}
//删除最老的节点
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
获取缓存
获取缓存调用LRUCache的get方法,其会根据key去map中查找对应值,如果没有,则会调用create方法尝试创建默认值,如果创建了默认值,则会返回默认值,并重新走一遍trimToSize,保证内存缓存大小在maxSize限制内。
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;
}
}
删除缓存
删除缓存主要涉及三件事:
- 从map中删除该节点
- entryRemove方法通知使用者该节点已被移除
- 重新计算当前缓存的大小
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;
}
LinkedHashMap如何实现LRU算法
LinkedHashMap继承于HashMap,同时内部自定义了LinkedHashMapEntry节点形成了双向链表结构,其内部有head和tail两个指向头和尾节点的指针,通过accessOrder变量来控制是否顺序。
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
LinkedHashMapEntry<K,V> before, after;
LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
transient LinkedHashMapEntry<K,V> head;
transient LinkedHashMapEntry<K,V> tail;
//true表示按照访问顺序,false表示按照插入顺序
final boolean accessOrder;
那么如何LinkedHashMap如何更新节点顺序呢?我们看下get方法的流程就知道了。
public V get(Object key) {
Node<K,V> e;
//调用父类HashMap的getNode方法获取key对应的节点,该方法会根据hash去对应的桶内查找key相等的节点
if ((e = getNode(hash(key), key)) == null)
return null;
//如果accessOrder=true,则表示要按照访问顺序对节点排序
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
get方法做了两件事:
- 调用父类HashMap的getNode方法获取key对应的节点
- 如果获取到节点值且accessOrder=true,则调用afterNodeAccess方法,该方法会对节点排序
接下来看下afterNodeAccess方法
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
//如果tail节点不等于当前节点
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
//将p节点从链表中删除
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
//更新tail节点指向p,即当前p变成了尾节点,即最新使用的节点,当LRUCache中大小超过maxSize时,会调用eldest方法找到最近最少使用的节点。实际上就是返回head节点
tail = p;
++modCount;
}
}
public Map.Entry<K, V> eldest() {
return head;
}