网友通过本文主要向大家介绍了android lrucache,lrucache缓存,lrucache源码,lrucache,lrucache原理等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com
Android 缓存类LruCache源码分析
1.引言
Android开发难免会遇到加载网络图片问题,而加载网络图片难免会遇到多次重复请求加载同一张图片问题,这么一来就导致多次加载网络,不仅浪费资源而且给用户体验感觉加载图片慢。从Android3.1开始,API中多了一个LruCache类,该类是一个使用最近最少使用算法的缓存类。也就是可以把上次下载的图片以键值对的方式保存在缓存中,以便下载加载同一张图片时无须再次从网络上下载,而且加载速度快。
2.LruCache源码详解
这里我把LruCache源码贴出来,几乎每行代码的注释都有,很详细,有意者可以仔细跟着注释阅读源码,以便理解LruCache类的原理。
/**
1.从类名LruCache就知道,该类的作用是一个最近最少使用算法来维护的一个缓存类。
2.该类是一个用于缓存一定数量的值,并且该缓存对象是持有强引用。
3.每当成功get一次值时,该值都会移动到链表的头部,以便标记该值为最近最新的值。
4.当缓存满了时,链表尾部的值会被认为是最近最少使用的值,会被从链表中移除,以便缓存有空间保存新的值。
5.缓存中链表的值发生改变时会调用空方法entryRemoved,开发者可以重写该方法,以便做相应的操作。
6.开发者应该去重写该类中sizeOf方法,该方法返回每个key对应value值得大小,以便该类去维护缓存的大小。
7.该类是一个安全类。同时该类不允许key和value为空。该类在Android3.1之后添加到源码中。
**/
package android.util;
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache {
private final LinkedHashMap map;
/** Size of this cache in units. Not necessarily the number of elements. */
private int size;//已使用缓存大小
private int maxSize;//总的缓存大小
private int putCount;//添加记录次数
private int createCount;//创建次数
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(0, 0.75f, true);//创建链表缓存,该链表缓存是整个LruCache类的重点。
}
/**
* Sets the size of the cache.
* @param maxSize The new maximum size.
*重新设置缓存大小
* @hide
*/
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.
* 根据key值获得缓存中对应的数据,该方法是线程安全的。
*/
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);//创建新的数据,默认返回null,创建的时候可以重写该方法,以便未命中的时候创建一个新的数据添加到缓存链表中。
if (createdValue == null) {
return null;//未命中时默认到这里结束。
}
//以下代码是在未命中时创建新数据添加到链表缓存中。
synchronized (this) {
createCount++;//标记创建新数据的次数
mapValue = map.put(key, createdValue);
//如果链表中已有该key对应的值,则最后取消添加新创建的值。很多人可能感到奇怪,此时链表中应该不存在key对应的值啊?其实这里是为了防止多线程操作导致数据不同步而添加的安全代码。不懂得自己体会去吧!
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
//标记已经使用了的内存大小。
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
//链表中发生数据变化时(调用了put,get方法)调用该方法。该方法默认是个空,开发者可以重写该方法在链表中数据变化时做相应的操作。
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) {//链表中已存在key对应的值则替换原来的值
size -= safeSizeOf(key, previous);//标记已使用的内存大小
}
}
if (previous != null) {
//链表中数据发生交换时调用该方法。
entryRemoved(false, key, previous, value);
}
//整理链表维护的内存大小
trimToSize(maxSize);
return previous;
}
/**
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
* 管理链表中内存的大小
*/
private 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) {
break;
}
// BEGIN LAYOUTLIB CHANGE
// get the last item in the linked list.
// This is not efficient, the goal here is to minimize the changes
// compared to the platform version.
Map.Entry toEvict = null;
//for循环得到链表末尾的数据,然后移除它。
for (Map.Entry entry : map.entrySet()) {
toEvict = entry;
}
// END LAYOUTLIB CHANGE
if (toEvict == null) {
break;
}
//移除链表末尾的一个数据,该数据就是最近最少使用到的数据。
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);//移除数据
size -= safeSizeOf(key, value);//重写计算已使用的内存大小
evictionCount++;//标记移除数据的次数
}
//开发者可以重新改方法,当移除数据时做相应的操作。