您的位置:首页 > 其它

LinkedHashMap最佳实践:LruCache

2016-10-08 15:59 85 查看
一句话解释:LruCache(least recently used cache)最近最少使用缓存。

前面,我们一起学习了LinkedHashMap数据结构,那么LruCache就是LinkedHashMap的最佳实践,童鞋们可以查看我的博客线性表数据结构解读(六)链式哈希表结构-LinkedHashMap学习一下。

在日常开发中,我们经常会使用一种内存缓存技术,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

而谷歌大概从SDK21开始,提供LruCache这个工具类(此类在android-support-v4的包中提供) ,用于作为实现内存缓存技术的解决方案。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

源码解读

OK老规矩,我先带大家一起研读下LruCache的源码,我们重点看下get、put、Remove等方法,其实原理就是LinkedHashMap的机制。

public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;// 声明一个LinkedHashMap
private int size;// 已经存储的数量大小
private int maxSize;// 规定的最大存储空间
private int putCount;// put的次数
private int createCount;// create的次数
private int evictionCount;// 回首的次数
private int hitCount;// 命中的次数
private int missCount;// 丢失的次数
/**
* 指定最大内存的LruCache构造方法
* @param maxSize for caches that do not override {@link #sizeOf}, this is
*     the maximum number of entries in the cache. For all other caches,
*     this is the maximum sum of the sizes of the entries in this cache.
*/
public LruCache(int maxSize) {// 官方推荐maxSize一般声明为手机内存的1/8
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/**
* Sets the size of the cache.
* @param maxSize The new maximum size.
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
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;
}
}

/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
* @return the previous value mapped by {@code key}.
*/
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;
}

/**
* 移除最老的元素,直到剩余元素数量等于或小于请求所需的大小
* Remove the eldest entries until the total of remaining entries is at or
* below the requested size.
* @param maxSize the maximum size of the cache before returning. May be -1
*            to evict even 0-sized elements.
*/
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 || 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);
}
}

/**
* 移除已存在的元素实体
* Removes the entry for {@code key} if it exists.
* @return the previous value mapped by {@code key}.
*/
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;
}
……
}


上面的关于LruCache初始化分配缓存大小有多少,可以参考下面几个因素:

你的设备可以为每个应用程序分配多大的内存?

设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?

你的设备的屏幕大小和分辨率分别是多少?

图片的尺寸和大小,还有每张图片会占据多少内存空间?

图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。

基本使用

Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。当cache已满的时候加入新的item时,在队列尾部的item会被回收。

int cacheSize = 4 * 1024 * 1024; // 4MiB
LruCache bitmapCache = new LruCache(cacheSize) {
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();

}
}


创建一个大小为4M的存储空间来进行图片的存储,存储按照队列的形式,后存储进来的和最新使用过的将会放在队列的最后,这样陈旧数据放在队列的开始,用于GC的回收。

synchronized (cache) {
if (cache.get(key) == null) {
cache.put(key, value);

}}


这个方法也展示了怎样规范化的使用以及获取由LruCache保存的数据,由于这个类是线程安全的所以需要加上同步块来进行存放数据,通过get和put方式来进行数据的存取,这点跟Map是一致的,put时如果键相同则会进行数据的覆盖,但是有点需要注意这里key和value都不能为空,这里跟Map有点区别。

还必须注意必须要主动的释放资源,如果你cache的某个值需要明确释放,重写方法

entryRemoved (boolean evicted, K key, V oldValue, V newValue)


如果资源是被系统回收的则evicted会返回TRUE,如果是由put,remove的方式替换回收的则evicted会返回FALSE,然后怎么知道是通过put还是remove的,可以通过对newValue是否为空进行判断,如果为空则是put调用,然后将remove和系统回收时将资源置为空,就要自己去实现了。

如果key相对应的item丢掉啦,重写create().这简化了调用代码,即使丢失了也总会返回。默认cache大小是测量的item的数量,重写sizeof计算不同item的大小。

参考链接:http://blog.csdn.net/linghu_java/article/details/8574102
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息