日常我们写程序的时候经常会使用到网络的图片,如果我们每次都去网上加载,那么性能难免会差一些,并且网络情况并不是总是 那么好,那么这时候我们就需要使用缓存了,我们学习android都知道图片的三级缓存,分别是内存缓存,硬盘缓存,网络缓存。
它的大体流程是这样的,给定一个网址,加载一张图片
- 如果内存缓存中存在,那就取出来,放上去,如果没有就找硬盘缓存
- 如果硬盘缓存中存在,那就取出来,放上去,并添加到内存缓存中,如果没有就请求网络
- 请求网络,把图片放上去,存一份到内存缓存和硬盘缓存中
接下来我们来分析一下到底是怎么实现的
一、LruCache
LruCache使用的是LRU算法,也叫最近最少使用算法,就是不断往里面存东西,超过上限,把最近最少的对象先淘汰,DiskCache使用的也是该算法。
LruCache的使用很简单
int maxMemory = (int) (Runtime.getRuntime().totalMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
首先得到程序最大可使用的内存空间,然后计算出内存缓存使用的空间,通常设置为最大可用内存的八分之一,然后实例化一个LruCache对象,和HashMap一样,因为里面就是用LinkHashMap实现的(之后会讲),需要指定键值对类型,传入缓存可用空间大小,并实现sizeof方法,对存入的对象的大小进行计算。
源码分析
1.构造函数
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);
}
实例化的时候传入的可用空间大小在构造函数中被赋值给成员变量,并示例化了一个LinkHashMap,起始容量为0,负载因子为0.75,并将accessOrder设置为了true
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
LinkHashMap默认是插入顺序的,当把accessOrder设置为true的时候就变成了访问顺序。
public V get(Object key) {
LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
当LinkHashMap调用get方法时会调用recordAccess方法
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
该方法会将该元素删除并添加到队列头部
2.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) {
//插入的缓存对象值加1
putCount++;
//增加已有缓存的大小
size += safeSizeOf(key, value);
//向map中加入缓存对象
previous = map.put(key, value);
//如果已有缓存对象,则缓存大小恢复到之前
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
//entryRemoved()是个空方法,可以自行实现
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//调整缓存大小(关键方法)
trimToSize(maxSize);
return previous;
}
put方法主要做了三件事,第一计算当前已用空间,第二讲对象存入,第三调整缓存
3.trimToSize方法
public void trimToSize(int maxSize) {
//死循环
while (true) {
K key;
V value;
synchronized (this) {
//如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()+ ".sizeOf() is reporting inconsistent results!");
}
//如果缓存大小size小于最大缓存,或者map为空,不需要再删除缓存对象,跳出循环
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);
}
}
在该方法中会不断取队尾的元素进行移除操作,直到当前缓存大小小于最大缓存空间大小。
4.get方法
public final V get(K key) {
//key为空抛出异常
if (key == null) {
throw new NullPoin