在实际开发中,缓存机制使用最频繁的便是图片缓存!目前大部分的App都是图文结合,从web服务器获取文字和图片,文字显示很快,图片基本上是先下载到手机本地,然后再显示,如果图片很多、很大,每次加载同一张图片,都去网络下载,那么App渲染的速度是比较慢的,这样的体验很差!所以,类似这样的场景,便要使用缓存机制!
目前缓存机制使用大致流程是,当App需要加载某一张图片时,先去手机内存中去找该图片,如果有,那么直接显示,如果无,则去手机sd卡或者手机外部存储中找该图片,如果有,那么直接显示,如果无,那么此时才去网络下载该图片。这种机制常称为三级缓存策略。
LruCache这个类在android.util包下,是API level 12引入的,对于API level 12之前的系统可以使用support library中的LruCache。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
//设置缓存的大小,基本上设置为手机内存的1/8
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
//LruCache里面的键值对分别是URL和对应的图片
LruCache mCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//在每次存入缓存的时候调用,计算出要缓存的每张图片的大小
return value.getByteCount();
}
};
LruCache内部的缓存实际是由LinkedHashMap来维护的,下面是LinkedHashMap的构造函数
public LinkedHashMap(int initialCapacity, //初始容量
float loadFactor, //加载因子
boolean accessOrder) { //排序方式,true则按访问顺序,为false,则按插入顺序
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
下面是Lrucache的构造方法
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap(0, 0.75f, true); //LruCache默认为按访问排序
}
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++; //put方法调用次数加1
size += safeSizeOf(key, value); //safeSizeOf()方法最终会调用创建LruCache时
//重写的SizeOf()方法并返回该方法的值
previous = map.put(key, value); //执行该方法时内部会判断该值是否已存在
if (previous != null) { //该值若存在会赋值给previous,否则为空
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value); //该方法需要在创建LruCache时重写,若不重写,则不作任何操作。
}
trimToSize(maxSize); 调整缓存大小(关键方法)
return previous;
}
可以看到put()方法并没有什么难点,重要的就是在添加过缓存对象后,调用 trimToSize()方法,来判断缓存是否已满,如果满了就要删除近期最少使用的算法。
trimToSize()方法
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
//如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
//如果缓存大小size小于最大缓存,或者map为空,不需要再删除缓存对象,跳出循环
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);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
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++; //get获取缓存命中时次数加1
return mapValue; //获取到值跳出方法返回取到的值
}
missCount++; //get获取缓存未命中时次数加1
}
V createdValue = create(key); //create()方法需要重写,不重写则返回null
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++; //create()方法成功调用次数加1
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;
}
}
LinkedHashMap中的get()方法
public V get(Object key) {
LinkedHashMapEntry e = (LinkedHashMapEntry)getEntry(key);
if (e == null)
return null;
e.recordAccess(this); //实现排序的关键方法
return e.value;
}
$emsp;recordAccess()方法
void recordAccess(HashMap m) {
LinkedHashMap lm = (LinkedHashMap)m;
//判断是否是访问排序
if (lm.accessOrder) {
lm.modCount++;
remove(); //删除此元素
addBefore(lm.header); //将此元素移动到队列的头部
}
}
以上便是LruCache实现的原理,理解了LinkedHashMap的数据结构就能理解整个原理。如果不懂,可以先看看LinkedHashMap的具体实现。