LruCache是实现了LRU算法的数据结构,方便开发者使用。LruCache实例化的时候,需要传入缓存最大值的,这个最大值可以表示缓存元素的个数,也可以表示所以缓存元素的总大小,这个是根据实际业务需求来的,比如缓存Bitmap的话,我们需要关心Bitmap占用内存的大小,而不是有几个Bitmap。
Android LruCache和Glide LruCache实现上基本上差不多,细节上有些差异,我们把主要方法分析一下:
Android LruCache
/**
* 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;
}
Glide LruCache
/**
* Adds the given item to the cache with the given key and returns any previous entry for the
* given key that may have already been in the cache.
*
* <p>If the size of the item is larger than the total cache size, the item will not be added to
* the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with
* the given key and item.
*
* @param key The key to add the item at.
* @param item The item to add.
*/
@Nullable
public synchronized Y put(@NonNull T key, @Nullable Y item) {
final int itemSize = getSize(item);
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}
if (item != null) {
currentSize += itemSize;
}
@Nullable final Y old = cache.put(key, item);
if (old != null) {
currentSize -= getSize(old);
if (!old.equals(item)) {
onItemEvicted(key, old);
}
}
evict();
return old;
}
put的核心作用就是把key和value放到LinkedHashMap中,同时要更新LruCache维护的已缓存大小(Android LruCache是size字段,Glide LruCache是currentSize字段),具体而言就是加上value的大小减去previous(如果有的话)的大小。最后,trimToSize去检查缓存有没有超限。
Android LruCache
/**
* 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;
}
}
Glide LruCache
/**
* Returns the item in the cache for the given key or null if no such item exists.
*
* @param key The key to check.
*/
@Nullable
public synchronized Y get(@NonNull T key) {
return cache.get(key);
}
get很简单了,就是从LinkedHashMap中取值。Android LruCahce提供了一个子类可以重写的create方法,让取不到值的时候,可以创建后再返回。
/**
* 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;
}
/**
* Removes the item at the given key and returns the removed item if present, and null otherwise.
*
* @param key The key to remove the item at.
*/
@Nullable
public synchronized Y remove(@NonNull T key) {
final Y value = cache.remove(key);
if (value != null) {
currentSize -= getSize(value);
}
return value;
}
remove直接看源码就行
Android LruCahce
/**
* 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);
}
}
Glide LruCache
/**
* Removes the least recently used items from the cache until the current size is less than the
* given size.
*
* @param size The size the cache should be less than.
*/
protected synchronized void trimToSize(long size) {
Map.Entry<T, Y> last;
Iterator<Map.Entry<T, Y>> cacheIterator;
while (currentSize > size) {
cacheIterator = cache.entrySet().iterator();
last = cacheIterator.next();
final Y toRemove = last.getValue();
currentSize -= getSize(toRemove);
final T key = last.getKey();
cacheIterator.remove();
onItemEvicted(key, toRemove);
}
}
trimToSize的作用就是让已缓存大小小于等于最大缓存大小,它会把最近最少使用的元素给删除,每次put的时候,都需要检查。这样的话,LruCache达到最大缓存值的时候,以后put都很有可能要执行删除操作。
研究Android LruCache源码发现的bug(其实不是bug,是对的)
总结一下:LruCache其实是对LinkedHashMap的封装,自己内部维护了一个已缓存大小size,在put的时候,检查一下以已缓存大小是否超限,如果超限就把LinkedHashMap的对头(最近最少使用元素)给删除。其实,最核心的东西在LinkedHashMap中,所以得研究LinkedHashMap。