首先呢?看这篇文章之前大家先对LinkedHashMap以及LruCache了解一下先,先看下面两篇文章哈:
Java LinkedHashMap工作原理及实现 | Yikun
Android高效加载大图、多图解决方案,有效避免程序OOM - CSDN博客
先会用再分析是最好的!!!
源码行动开始,看下面(到这里假设你已经是LinkedHashMap和LruCache有所了解的哦)
源码分析
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
/** Size of this cache in units. Not necessarily the number of elements. */
//这个缓存大小的单位,不一定是元素的个数(这句话的意思是默认是返回元素的个数sizeOf()方法默认返回1,但如果我们根据需求重写可能会返回容量等)
private int size; //当前缓存的值(即是当前缓存的值)
private int maxSize;//最大值,可以通过构造方法或者resize方法传进来
private int putCount;//添加到缓存中的个数
private int createCount; //创建的个数
private int evictionCount; //被移除的个数
private int hitCount; //命中个数,其实就是累计的查询次数
private int missCount;//丢失个数
/**
* @param maxSize 缓存的最大值
*/
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//初始化构造方法,看得出它是基于LinkedHashMap的,还有就是按照访问顺序
//(即根据时间先后)进行排列的而不是访问元素次数多少排列
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/**
* 重新设置缓存最大值-跟调用trimToSize()方法效果一样的
*/
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对应的值存在(或者之后create(String key)创建),则返回这个对应值
//,并且这个值会被加到会被移动队列头部(linkedHashMap是双向链表,所以头看起来也是尾,
//但从正常角度来看,把最近访问的元素都会保存到tail指针那里去,即我们说的尾巴,
//写这个文章的外国佬对这个理解不一样,他认为tail指针所在的那边也是队列头来的);
//返回null情况有两种,一种是缓存中没有对应的值,另外一种是create(String key)方法没有创建值出来
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;//映射值,这里是指的是map的value值
synchronized (this) {
//加把锁是为了防止多线程访问时候map.get(key)操作过多,造成hash演算查找负担过大
mapValue = map.get(key);
if (mapValue != null) { //存在的话
hitCount++;//命中值加1,其实就是累计查询值+1
return mapValue; //---注释1---
}
//mapValue值不存在,说明已经丢失了,这里可能很多小伙伴会问,
//为什么不可以value为null呢? 虽然LinkedHashMap 的 key/value 值都可以为null的,
//但很精妙就是LruCache的put方法已经规定key/value都不可以为null存入去,
//丢失的可能是我们调用了bitmap.recycle()回收了等情况丢失了
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.
*/
//上面意思:尝试去通过create(String key)方法去创建一个value,这可能需
//要花费一点时间去创建,如果创建不成功,就返回null;如果创建成功,
//那么除了返回这个新的非null值,还会把这个值留在map中
//这里很多人都会问:为什么synchronized 锁来创建呢?
//创建不锁的话,多线程访问就会创建很多个createdValue 出来啦。
//是的,你说的没错,但很多时候我们都是单线程访问,即使是多线程操作,
//我们也很少重写create(String key) 方法,
//所以我个人认为他这样设计是很好的
V createdValue = create(key);
if (createdValue == null) {
return null;
}
//锁这里我决定是担心多线程访问同时(ms差值忽略)put的太多次导致hash演算
//压力,加一把锁好一点的啦
synchronized (this) {
createCount++; //创造加1,累积创建的value值
//mapValue 是旧值,createdValue是新值
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
// 上面意思:有冲突所以撤销之前的操作
//为什么会有冲突呢?这里就是多线程调用createdValue(String key)
//导致createdValue 很多个,如果之前map已经存在了key对应的value ,
//说明是多线程操作的;很多小伙伴会问:为什么不可以呢?
//哇咔咔,你在看看以前的代码,之前要是有值(看---注释1---),
//早已经调用return结束了,这里有值就说明了是
//createdValue(String key)创建出来的非null值,
//至于为什么会多个,因为多线程同时调用create(key)创造出来的,
//但这里是同步代码块(加了synchronized锁),
//在一个时间只能一条线程进入访问,并且能够来到这里,
//说明同步代码块不是第一次被访问了(但值的确是第一个线程的)
map.put(key, mapValue);
} else {
//说明之前这个key对应的value是空的,
//所以这个缓存值应该累积一下,
//还有就是:第一个线程(或者说第一个访问这个同步代码块的线程)走的就是这个方法
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
//这个方法void entryRemoved(boolean evicted, K key, V oldValue, V newValue) 是这样的,
//这个方法调用了多次,但这里是很特殊的,大家看到下面方法里面的参数
//createdValue, mapValue位置有点小变化,因为它不想调用trimToSize()
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
//控制 size<=maxSize,下面会详说
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移到队列头(这里指的是tail指针那边的队列头部,
//我们也可以理解为队列尾巴,毕竟tail的中文意思就是尾巴),不必纠结这个,
//因为LinkedHashMap是双向链表
//返回的是map的先前存储的值previous
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);
}
//下面会讲解,为了使得size<maxSize
trimToSize(maxSize);
return previous;
}
/**
* 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.
*/
//上面意思:在size>=maxSize时候,移除最久不访问的元素(即那些不常访问的元素),
//这里注意哦,是一个一个删除;当size<maxSize停止
//如果maxsize为-1时候,就会清空lruCache里面的元素出去
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<=maxSize 就停止啦
break;
}
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
//这里就是toEvict 不为 null 了
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);//不断削减
evictionCount++;//削减数+1
}
//下面分析这个办法
entryRemoved(true, key, value, null);
}
}
/**
* Removes the entry for {@code key} if it exists.
*
* @return the previous value mapped by {@code key}.
*/
//删除这个key对应的值,无论value是否为null,都会删除
// 当value不在entry可以找到,key也是被map抹除
//注意:value为null存储在map中是可以存储以及找到的,这里不要混淆哈
//map可以key/value为null,但LruCache不容许,因为LruCache的put做了处理
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.
*
* <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}. Otherwise it was caused by
* an eviction or a {@link #remove}.
*/
//说了那么多,其实意思就是remove或者驱逐(evicted)出来,
//我们没有通知垃圾回收器去回收它,如果你想回收,就需要自己重写这个办法.
//至于怎么用,下面这篇文章最后讲解了
// http://blog.csdn.net/jxxfzgy/article/details/44885623
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;
}
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.
*/
//这个方法默认返回1,开发中99%会重写这个办法,还有1%是忘了重写了
protected int sizeOf(K key, V value) {
return 1;
}
/**
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
*/
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
/**
* For caches that do not override {@link #sizeOf}, this returns the number
* of entries in the cache. For all other caches, this returns the sum of
* the sizes of the entries in this cache.
*/
public synchronized final int size() {
return size;
}
/**
* For caches that do not override {@link #sizeOf}, this returns the maximum
* number of entries in the cache. For all other caches, this returns the
* maximum sum of the sizes of the entries in this cache.
*/
public synchronized final int maxSize() {
return maxSize;
}
/**
* Returns the number of times {@link #get} returned a value that was
* already present in the cache.
*/
public synchronized final int hitCount() {
return hitCount;
}
/**
* Returns the number of times {@link #get} returned null or required a new
* value to be created.
*/
public synchronized final int missCount() {
return missCount;
}
/**
* Returns the number of times {@link #create(Object)} returned a value.
*/
public synchronized final int createCount() {
return createCount;
}
/**
* Returns the number of times {@link #put} was called.
*/
public synchronized final int putCount() {
return putCount;
}
/**
* Returns the number of values that have been evicted.
*/
public synchronized final int evictionCount() {
return evictionCount;
}
/**
* Returns a copy of the current contents of the cache, ordered from least
* recently accessed to most recently accessed.
*/
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);
}
}
实战
看完了源码分析,肯定需要实践一下的,看这篇文章最后那里:
LruCache详解之 Android 内存优化 - CSDN博客
分析玩LruCache之后,希望你可以看看下面几篇文章:
1.Android高效加载大图、多图解决方案,有效避免程序OOM - CSDN博客
2.Android DiskLruCache完全解析,硬盘缓存的最佳方案 - CSDN博客
3. Android照片墙应用实现,再多的图片也不怕崩溃 - CSDN博客