LruCache详解以及实战

xiaoxiao2021-02-28  75

首先呢?看这篇文章之前大家先对LinkedHashMap以及LruCache了解一下先,先看下面两篇文章哈:

Java LinkedHashMap工作原理及实现 | Yikun Android高效加载大图、多图解决方案,有效避免程序OOM - 博客

先会用再分析是最好的!!!

源码行动开始,看下面(到这里假设你已经是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 内存优化 - 博客

分析玩LruCache之后,希望你可以看看下面几篇文章: 1.Android高效加载大图、多图解决方案,有效避免程序OOM - 博客 2.Android DiskLruCache完全解析,硬盘缓存的最佳方案 - 博客 3. Android照片墙应用实现,再多的图片也不怕崩溃 - 博客

转载请注明原文地址: https://www.6miu.com/read-2627363.html

最新回复(0)