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

Android 源码解析-LruCache 缓存工具类

2017-10-25 16:37 295 查看
关于Android的三级缓存,其中主要的就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LruCache算法,今天我们就从使用到源码解析,来彻底理解Android中的缓存机制。

1、LruCache的介绍

LruCache是Android API 12以后提供的一个缓存工具类,采用了最近最少使用算法。它把最近使用的对象用“强引用”存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前就从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。

2、LruCache的使用

以图片缓存为例

private LruCache<String, Bitmap> mMemoryCache;

public MemoryCache() {

final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

int cacheSize = maxMemory / 4;

mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}


第一步 设置缓存大小,一般为当前进程可用容量的1/8。

第二步 重写sizeOf方法,计算出要缓存的每张图片的大小。

3、LruCache 的实现原理

LruCache 使用LinkedHashMap维护一个列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。如下图



LinkedHashMap中双向链表的结构是访问顺序实现,accessOrder = ture

LinkedHashMap<Integer, Integer> linkedHashMap = new LinkedHashMap<>(10, 0.75f, true);

linkedHashMap.put(0, 0);
linkedHashMap.put(1, 1);
linkedHashMap.put(2, 2);
linkedHashMap.put(3, 3);
linkedHashMap.put(4, 4);
linkedHashMap.get(1);
linkedHashMap.get(2);
linkedHashMap.put(5, 5);

for (Map.Entry<Integer, Integer> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}


4、LruCache 的源码解析

构造方法与属性变量

private final LinkedHashMap<K, V> map;

/** Size of this cache in units. Not necessarily the number of elements. */
private int size; //已存储的大小
private int maxSize; //最大的存储空间

private int putCount;    //put放大调用的次数
private int createCount; //create 的次数
private int evictionCount;   //回首的次数
private int hitCount;    //命中的次数
private int missCount;   //丢失的次数

/**
* @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) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}


put 方法

/**
* 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) { //key 和 value 不能为null
throw new NullPointerException("key == null || value == null");
}

V previous;
synchronized (this) {
putCount++;  //put 后值加一
size += safeSizeOf(key, value); //增加当前缓存的大小
previous = map.put(key, value); //加入缓存对象
if (previous != null) { // 如果cache 中存在对象则恢复到之前的大小
size -= safeSizeOf(key, previous);
}
}

if (previous != null) {  //空算法
entryRemoved(false, key, previous, value);
}

trimToSize(maxSize);  //调整缓存大小
return previous;
}


trimToSize(int maxSize)

/**
* 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) { //如果当前的size 小于最大size 跳出循环
break;
}

Map.Entry<K, V> toEvict = map.eldest(); //取队尾的元素,近期最少访问的元素
if (toEvict == null) {
break;
}

key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key); //删除队尾的元素并更新当前size 的大小
size -= safeSizeOf(key, value);
evictionCount++;
}

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


get(K key)

/**
* 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); //实现被访问的元素更新到队头部的功能 具体实现看LinkedHashMap的实现方法
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}


这样我们就完成了对LruCache的使用,现在我们发现这个缓存类也没那么复杂,用法也十分简单,正是因为简单,所以我们可以很方便的对其进行扩展。当然了,这仅仅是做了内存缓存,熟悉缓存机制的朋友一定会知道磁盘缓存和内存缓存二者的关系,有关磁盘缓存的问题我将在以后的文章中进行讲述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: