LruCache源码及常用方法原理
特点
- 基于LinkedHashMap保存内容和实现排序
- 当缓存无空间,会将最少使用的元素移除
- 线程安全
常用方法原理
LruCache维护了一个LinkedHashMap用来保存元素,除此之外还有一些用来记录状态、长度等的成员变量
@UnsupportedAppUsage
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;
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<K, V>(0, 0.75f, true);
}
只有一个构造器,没有无参构造器。构造器中对最大容量和用来保存元素的map进行了初始化,可以看到LinkedHashMap中accessOrder
参数为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) {
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;
}
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
/**
* 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) {
break;
}
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
put方法首先进行一次空判断,然后在同步代码块中进行判断。首先putCount
自增,通过safeSizeOf
方法获取元素大小并加到size
变量中(safeSizeOf
本质是调用sizeOf方法,并确保大小不会小于0)。将键值对插入map
中,并获得被替换出的元素。(若存在被替换元素,则size还需减去被替换出的元素大小)
若存在被替换的元素,则将其通过entryRemoved()
回调抛出。最后通过trimToSize()
方法整理内存。
观察trimToSize()
,该方法可用用来指定最大容量来调整缓存,其内部是一个循环,首先判断状态,并在当前缓存大小小于等于最大缓存时跳出循环。取出map
中最老的元素(若为空跳出循环),将其回收,并将size
减少该元素的大小,通过entryRemoved()
回调抛出事件。
GET方法
/**
* 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;
}
}
get在同步代码块内尝试获取目标值,若获取成功则直接返回,否则尝试通过create()
回调创建元素。若返回为null则直接返回空,若不为空,则尝试将该生成的元素插入map
(map内该key不冲突,否则撤回插入)。最后若key冲突则返回原缓存,并通过entryRemoved()
回调将创建失败的事件抛出,否则整理内存后,将创建的元素抛出
缓存获取后,排序由LinkedHashMap实现,具体需要去看LinkedHashMap源码
常用回调方法
为了在缓存使用过程中监听各种情况事件,LruCache提供了一些可重写的回调方法
/**
* 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.
*
* <p>The method is called without synchronization: other threads may
* access the cache while this method is executing.
*
* @param evicted true if the entry is being removed to make space, false
* if the removal was caused by a {@link #put} or {@link #remove}.
* @param newValue the new value for {@code key}, if it exists. If non-null,
* this removal was caused by a {@link #put} or a {@link #get}. Otherwise it was caused by
* an eviction or a {@link #remove}.
*/
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.
*/
protected V create(K key) {
return null;
}
/**
* 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.
*/
protected int sizeOf(K key, V value) {
return 1;
}
entryRemoved
参数:
- evicted:布尔值,标记该事件是否是由于内存整理而回收触发的
- key:键
- oldValue:原元素
- newValue:新元素,该值在由于键冲突导致元素替换而导致的触发时会生效
该方法用来处理当缓存由元素被移除时触发的回调
create
参数:
- key:键
该方法用来当通过键获取元素为空时,提供一个创建元素的方法的回调,默认返回空
sizeOf
参数:
- key:键
- value:值
该方法用来通过键和值计算元素大小,返回值应大于0。默认返回1