版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_25859403/article/details/52145819
一、前面要说的
LruCache是Android提供的一种缓存算法,Lru就是Last Recently Use。
因此,LruCache就是通过条目的访问状况对条目进行排序,然后将最近访问(写入或者读取)的条目移动到队列头部(准确地应该是链表头部,下同),将最近最少使用的条目移动到队列尾部,然后在缓存被填满或者收到移除队尾的条目。
下面是源码 的分析,不过在看源码分析之前先了解一下几个名词:
cache hit: 缓存命中。在LruCache中,如果根据key值能够在map中找到相应的不为null的value, 那么,就为cache hit。
cache miss:缓存未命中。和上面相反。
eviction:驱逐 。所谓的驱逐可以理解为由于size超过了规定的maxSize而自动移除。
条目:entry
二、源码分析
package android.support.v4.util;
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<K, V> {
/**
* 问题:为什么采用LinkedHashMap来作为缓存的容器?
*/
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;// map的put方法被调用的次数,也可理解为发生条目置换的次数 。
private int createCount;// create方法被调用的次数
private int evictionCount;// 驱逐条目的次数(可参考trimToSize(...))
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;
/*
* 初始化一个LinkedHashMap,这个LinkedHashMap的
*
* 初始容量--0 ,
*
* 加载因子--0.75f ,
*
* 排序模式--插入顺序
*/
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.
*
* 译:根据key返回相应的value,如果这个value存在于缓存中或这个value可以通过create()方法创建出来,否则会返回null。
* 这个value被返回后,就会被移到队列的头部.
*
* 从代码中我们可以看出这个方法可能返回三种情况的值: 1.mapValue : 当
* 通过get(key)方法取出的value不为null的时候,就会返回mapValue,他就是取出来的value。
*
* 2.createdValue:当上面的mapValue为null,也就是说本来map中就不存在对应key值的value时,
* 就会调用create(key)方法创建一个value。然而,这并不够,默认情况下create(key)的返回值为null。
* 因此,如果想要返回这个createdValue就要重写create(key)方法,让create(key)返回一个不为null的值。
* 3.null:如果以上两者都为null,则返回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++;
return mapValue;
}
missCount++;
}
/*
*/
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}.
*
* 此方法用于将条目添加到缓存。先添加的value将会被移动到队列头部。
*
* 返回 被置换出来的旧value。
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous; // 用于存放替换出来的value。
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. 译:移除最老的条目,直到剩余条目的总数低于maxSize
*
* @param maxSize
* the maximum size of the cache before returning. May be -1 to
* evict even 0-sized elements.
*
* 方法解释: 整个trimToSize(...)中仅仅包含一个无限循环,
* 不断移除多余的旧条目,以保证size不超过maxSize。
*/
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
// 如果当前已使用的缓存<0或者(map没有条目且当前已使用的缓存不为0)
if (size < 0 || (map.isEmpty() && size != 0)) {
// 抛出一个IllegalStateException
throw new IllegalStateException(
getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}
// 如果已使用缓存数没有超过maxSize或者当前map中没有条目,那么师妹也不做,直接跳出本次循环
if (size <= maxSize || map.isEmpty()) {
break;
}
/*
* 当size>maxSize的时候,会执行下面的代码。
*/
// 取出map中最老的元素,即链表头上的元素。
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key); // 移除最老的元素
size -= safeSizeOf(key, value);// --> size-=1(默认情况);
evictionCount++;// 驱逐条目的次数
}
entryRemoved(true, key, value, null);
}
}
/**
* Removes the entry for {@code key} if it exists.
*
* @return the previous value mapped by {@code key}.
*
* 此方法用于移除一个缓存中存在的条目
*
* 返回被remove掉的条目的value。
*
*/
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;
}
/**
* Called for entries that have been evicted or removed. This method is
* invoked when a value is evicted to make space, removed by a call to
* {@link #remove}, or replaced by a call to {@link #put}. The default
* implementation does nothing.
*
* 当一个value被驱逐来释放空间,调用remove来移除或者调用put方法替换的时候会调用这个方法。 默认实现什么都不做。
*
*
* @param evicted
* true 表示给条目由于需要是释放空间而被移除(即是被驱逐的)--非正常的。 false表示条目是被通过remove(K
* key)移除的,或者put(K key, V value)置换掉--人为操作的。
*
* @param newValue
* 用于置换oldValue的value。因此,如果它不为null,这个entryRemoved()一定 是在put(K
* key, V value)中被调用的。
* 如果newValue为null,说明entryRemoved()是在trimToSize(int
* maxSize)或者remove(K key)中调用的。
*
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
}
/**
* Called after a cache miss to compute a value for the corresponding key.
* Returns the computed value or null if no value can be computed. The
* default implementation returns null.
*
* <p>
* The method is called without synchronization: other threads may access
* the cache while this method is executing.
*
* <p>
* If a value for {@code key} exists in the cache when this method returns,
* the created value will be released with {@link #entryRemoved} and
* discarded. This can occur when multiple threads request the same key at
* the same time (causing multiple values to be created), or when one thread
* calls {@link #put} while another is creating a value for the same key.
*
* 译:1.当cache miss 后,会根据相应的key值计算出一个新的value(下称为 createValue)。
* 此方法返回createValue或者null(value不能计算)。此方法默认返回null。
*
* 2. 这个方法是不同步的。
*
* 3.如果返回的createValue缓存中已经存在,那么这个createValue会被entryRemoved释放掉({@link #get(...) 132行})。
* 这里提示我们,有时候需要重写entryRemoved()方法来释放value。
*
*
*/
protected V create(K key) {
return null;
}
/**
* 此方法主要用于判断({@link #sizeOf(Object, Object)})返回值的正确性。
*
* 如果sizeOf()的返回值正常,则返回sizeOf()的返回值。否则抛出一个IllegalStateException。
*/
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
/**
* Returns the size of the entry for {@code key} and {@code value} in
* user-defined units. The default implementation returns 1 so that size is
* the number of entries and max size is the maximum number of entries.
*
* <p>
* An entry's size must not change while it is in the cache.
*
* 返回指定的条目的size,单位是用户自定义。 默认实现是返回1,这时候size表示entris的数量, max
* size表示entries的最大数量。entry的大小在缓存期间不能改变 一般情况下我们需要重写sizeOf方法按不同的单位来计算缓存。
*
*/
protected int sizeOf(K key, V value) {
return 1;
}
/**
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
*
* 此方法用于清除缓存,通过将maxSize设定为-1.
*
*
*/
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
public synchronized final int size() {
return size;
}
public synchronized final int maxSize() {
return maxSize;
}
public synchronized final int hitCount() {
return hitCount;
}
public synchronized final int missCount() {
return missCount;
}
public synchronized final int createCount() {
return createCount;
}
public synchronized final int putCount() {
return putCount;
}
public synchronized final int evictionCount() {
return evictionCount;
}
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
@Override
public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize, hitCount, missCount,
hitPercent);
}
}
三、 最后的问题
(一) 为什么采用LinkedHashMap来作为缓存的容器?
原因很简单,我们知道LinkedHashMap的实现采用了哈希表和双重链表。因此这使得LinkedHashMap具有两个特点:
1)唯一性
当然这里的唯一性是指key的唯一性,唯一性是由哈希表来保证的。
2)有序性 这里的有序仅仅代表 插入和读取的顺序一致,而不是在内部进行排序。有序性是由双重链表决定的。
这里重点说一下有序性。
public LinkedHashMap(int initialCapacity,//初始容量
float loadFactor,//加载因子
boolean accessOrder)//访问顺序
当accessOrder为true时表示按访问顺序排序;
当accessOrder为false时表示按插入顺序排序。
所谓的访问顺序中的就是将最近访问的元素移动到链表的尾部。