您的位置:首页 > 移动开发 > Android开发

Lrucache算法的原理简要分析

2017-08-15 15:51 239 查看

Lrucache算法能做什么?

LRU(Least Recently Used), 即近期最少使用算法。

大家都知道在Android手机上面,由于Android的一些开放性原因,所有都知道Android的内存是非常的宝贵,特别是像一些大图列表,许多大的强引用无法及时回收从而造成了内存的溢出。可能会有同学想到使用一些弱引用等来解决这类问题,但是在新的系统上,Android已经不能很好的支持这一操作了,所以Google给出了一个新的算法,lru算法就是为了更好解决这一问题。

Lru是最近最少使用算法的简称,意思呢就是查询出最近的时间使用次数最少的那个对象,然后在内存不够的时候回收掉它,从而不会引起内存的溢出,又能给出更好的用户体验。

Lrucache算法实现原理是什么?

以下会从lru算法中的源码来简单的分析说明。这里我只说说里面几个我觉得和最近最少非常相关的几个方法,至于更多详细的源码解析请参考

链接:http://www.cnblogs.com/liuling/archive/2015/09/24/2015-9-24-1.html

好,由于源码里面的变量和常量太多,这里我贴出主要的几个常量和变量:

//存放数据的集合
private final LinkedHashMap<K, V> map;
//当前LruCahce的内存占用大小
private int size;
//Lrucache的最大容量
private int maxSize;
//put的次数
private int putCount;
//create的次数
private int createCount;
//回收的次数
private int evictionCount;
//命中的次数
private int hitCount;
//丢失的次数
private int missCount;


然后我们来看看初始化的地方:构造函数

public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//将LinkedHashMap的accessOrder设置为true来实现LRU的
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}


我们从构造函数可以知道,这里初始化了一个LinkedHashMap对象,那么这个LinkedHashMap对象是实现Lru算法的关键。基本上整个算法都是基于LinkedHashMap来操作。

至于new LinkedHashMap\<\K, V>(0, 0.75f, true);的含义初始容量为零,0.75是加载因子,表示容量达到最大容量的75%的时候会把内存增加一半。最后这个参数至关重要。表示访问元素的排序方式,true表示按照访问顺序排序,false表示按照插入的顺序排序。按照访问顺序的意思就是,如果你先get(0),那么打印map,原来第0个角标的元素就是在最后一位了。

初始化讲完了,就到了讲讲关键的地方,也就是put方法,这里看看当容量增大了,lru算法是怎么实现内存最优的:

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); //把当前容量增加,增加值为value的大小
previous = map.put(key, value); //previous为旧的值
if (previous != null) {
size -= safeSizeOf(key, previous); //如果旧的值不为空,就把旧的值得大小减去
}
}

if (previous != null) {
entryRemoved(false, key, previous, value);
}
//每次新加入对象都需要调用trimToSize方法看是否需要回收
trimToSize(maxSize);
return previous;
}


上面方法,首先把size增加,然后判断是否以前已经有元素,如果有,就更新当前的size,并且调用entryRemoved方法,entryRemoved是一个空实现,如果我们使用LruCache的时候需要掌握元素移除的信息,可以重写这个方法。最后就会调用trimToSize,来调整集合中的内容,那么控制回收的方法就是trimToSize来判断了,接下来分析这个方法:

/**
*此方法根据maxSize来调整内存cache的大小,如果maxSize传入-1,则清空缓存中的所有对象
*/
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!");
}
//如果当前size小于maxSize或者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++;//回收次数+1
}

entryRemoved(true, key, value, null);
}
}


这个方法是一个无限循环,跳出循环的条件是,size < maxSize或者 map 为空。主要的功能是判断当前容量时候已经超出最大的容量,如果超出了maxSize的话,就会循环移除map中的第一个元素,直到达到跳出循环的条件。由上面的分析知道,map中的第一个元素就是最近最少使用的那个元素。

存和取是相对应的,既然有了存入内存的方法,那么就有取这个方法。下面分析分析get方法:

/**
*通过key获取相应的item,或者创建返回相应的item。相应的item会移动到队列的尾部,如果item的value没有被cache或者不能被创建,则返回null。
*
*/
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++;  //命中 + 1,mapValue不为空表示命中,hitCount+1并返回mapValue对象
return mapValue;
}
missCount++;//丢失+1,也就是未命中
}
/**
*如果未命中,则试图创建一个对象,这里create方法返回null,并没有实现创建对象的方法,如果需要事项创建对象的方法可以重写create方法。因为图片缓存时内存缓存没有命中会去文件缓存中去取或者从网络下载,所以并不需要创建。
*/
V createdValue = create(key); //创建
if (createdValue == null) {
return null;
}
//假如创建了新的对象,则继续往下执行
synchronized (this) {
createCount++;  //创建 + 1
//将createdValue加入到map中,并且将原来键为key的对象保存到mapValue
mapValue = map.put(key, createdValue);

if (mapValue != null) {
// There was a conflict so undo that last put
//如果有矛盾,意思就是有旧的值,就撤销put操作
map.put(key, mapValue);
} else {
//加入新创建的对象之后需要重新计算size大小
size += safeSizeOf(key, createdValue);
}
}

if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
//每次新加入对象都需要调用trimToSize方法看是否需要回收
trimToSize(maxSize);
return createdValue;
}
}


简单的说,就是这个方法就先通过key来获取value,如果能获取到就就直接返回,获取不到的话,就调用create()方法创建一个,事实上,如果我们不重写这个create方法的话是return null的,所以整个流程就是获取得到就直接返回,获取不到就返回null。

有了添加,就有减少,至于怎么减少?看代码:

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;
}


这个的意思就是从内存缓存中根据key值移除某个对象并返回该对象。

我们怎么使用Lruchace算法?

至于项目中怎么使用lru算法? 其实就是创建lru算法的对象,进行增删查而已,和map用法相似,简单粗暴点,直接贴代码,演示如何缓存你自己想要缓存的数据:

//这里面的大小是你希望使用的内存大小
LruCache<Integer, Bitmap> mBitmapCache = new LruCache<Integer, Bitmap>(2 * 1024 * 1024);
//存入bitmap
mBitmapCache.put(key, value);
//取出bitmap
mBitmapCache.get(key);


上面的内存大小需要注意, 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 所以可这么设定内存的大小:

// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);


好了,基本上lru算法就是这么一回事,简单的分析了一遍,今天就到这里,下班了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android