LruCache中的Lru指的是LeastRecentlyUsed,也就是近期最少使用算法。即当缓存容器中的数据大小达到指定的最大容量时,Lru会清楚最近使用最少的数据。
为什么要用LruCache?其实使用它的原因有很多,例如我们要做一个加载网络图片的listview时,如果我们不加节制的向服务器请求大量图片,那么对于服务器来说是一个不少的负担,其次,对于用户来说,每次刷新都意味着流量的大量消耗以及长时间等待,所以缓存机制几乎是每个需要联网的App必须做的。
LruCache已经存在于android3.1之后的API中,但为了兼容android3.1之前的版本,推荐使用support v4中提供的Lrucache 。而这个缓存只是一个内存缓存,并不能进行本地缓存,也就是说,如果内存不足,缓存有可能会失效,而且当App重启的时候,缓存会重新开始生效。如果想要进行本地磁盘缓存,推荐使用DiskLruCache,虽然没包含在官方API中,但是官方推荐我们使用,本文暂不讨论。
Lrucache的是使用很简单,最简单的就是实例化一个Lrucache对象,然后使用它的put()方法和get()方法。
例如:
// 构造方法传入当前应用可用最大内存的八分之一
LruCache<String, Bitmap> mLruCache = new LruCache(Runtime.getRuntime().maxMemory() / 1024 / 8);
稍微复杂点的做法可以重写以下几个方法,这样做可以更好的满足你的需要了
@Override
// 重写sizeOf方法,并计算返回每个Bitmap对象占用的内存
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024; //单位是kb
}
@Override
// 当缓存被移除时调用,第一个参数是表明缓存移除的原因,true表示被LruCache移除,false表示被主动remove移除,可不重写
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap
newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
}
@Override
// 当get方法获取不到缓存的时候调用,如果需要创建自定义默认缓存,可以在这里添加逻辑,可不重写
protected Bitmap create(String key) {
return super.create(key);
}
关于这几方法的重写我在下面的源码的解读中会指出重写的必要性。首先从Lrucache类的put()方法入手吧
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;
}
size += safeSizeOf(key, value);
在加入map之前会计算value的大小,看看这个私有方法的实现
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
int result = sizeOf(key, value);
sizeof()方法就是我们开始重写的,目的就是有我们去返回单个value的大小 。
我们重点来看下面的这行代码
previous = map.put(key, value);
map对象是LinkedHashMap extends HashMap
因此put()方法来源是HashMap类 ,我们看看put()方法在HashMap类中的实现
@Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
}
put方法中会做一个判断,如果table中存在key所对应的value就替换value,如果不存在就加入table中
,这里要关注的是preModify 和addNewEntry这两个方法,因为LinkedHashMap类对两个方法进行了重写,我们就看addNewEntry方法,这个方法中的实现懂了,preModify方法就简单了
@Override void addNewEntry(K key, V value, int hash, int index) {
LinkedEntry<K, V> header = this.header;
// Remove eldest entry if instructed to do so.
LinkedEntry<K, V> eldest = header.nxt;
if (eldest != header && removeEldestEntry(eldest)) {
remove(eldest.key);
}
// Create new entry, link it on to list, and put it into table
LinkedEntry<K, V> oldTail = header.prv;
LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
key, value, hash, table[index], header, oldTail);
table[index] = oldTail.nxt = header.prv = newTail;
}
这个方法的代码不多,我看的时候还是花了一点时间才看明白,它其实就是实现了一个双向链表
根据这图,map对象每加入一个entry时,就会在链表HEAD头节点的邻近右侧加入这个新节点,如果已经存在的entry时当调用put()方法,这个entry对应的节点就会移动到链表的最右侧,换句话说,即离HEAD节点越近,就是近期使用最少的。我们再来看看LinkedHashMap类是怎么重写preModify()方法的
@Override void preModify(HashMapEntry<K, V> e) {
if (accessOrder) {
makeTail((LinkedEntry<K, V>) e);
}
}
private void makeTail(LinkedEntry<K, V> e) {
// Unlink e
e.prv.nxt = e.nxt;
e.nxt.prv = e.prv;
// Relink e as tail
LinkedEntry<K, V> header = this.header;
LinkedEntry<K, V> oldTail = header.prv;
e.nxt = header;
e.prv = oldTail;
oldTail.nxt = header.prv = e;
modCount++;
}
而makeTail方法正是去修改了链表的指向 。
再来看看Lrucache类的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++;
}
/*
* 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对象的 get()方法的在LinkedHashMap类实现
@Override public V get(Object key) {
/*
* This method is overridden to eliminate the need for a polymorphic
* invocation in superclass at the expense of code duplication.
*/
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
if (e == null)
return null;
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
}
return null;
}
同样的,也去调用了makeTail方法将正在使用的entry对应的节点放到链表的最右侧。这里也验证了使用越频繁的节点越靠向链表的右侧,使用少的节点更靠近头节点 。
最后再来讲讲Lrucache容器达到max时是如何清理最少使用entry的
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);
}
}
这个方法在get(),put(),reSize()中都会有调用,它会判断Lrucache容器当前的大小是否大于maxSize,如果大于就逐个删除靠近Head的节点,否则就退出while循环。