LruCache原理分析
使用LruCache作为图片的内存缓存,其内部使用LinkedHashMap作为实现基础,并且全部使用强引用。弱引用、软应用在android API9之后会被更容易回收,使得有潜在浪费资源的情况。 ? 参考: http://blog.csdn.net/yudajun/article/details/41620647 难点: LinkedHashMap插入顺序理解。 对于访问顺序,为 true;对于插入顺序,则为 false 插入顺序排序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。 访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。 原理: 在获取get()、 存入put()方法调用后,都要调用trimToSize()方法,开启循环进行判断。 发现有溢出现象,则溢出头部元素,即最不常用元素。 源代码:(看注释)
package com.baiiu.test; import java.util.LinkedHashMap; import java.util.Map; public class HowLruCache<K, V> { private final LinkedHashMap<K, V> map; /** Size of this cache in units. Not necessarily the number of elements. */ private int size;// 已经使用的内存大小 private int maxSize;// 给lrucache分配的总内存大小,用于存储图片缓存 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 HowLruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; /** * 该哈希映射的迭代顺序就是最后访问其条目的顺序,<BR> * 从近期访问最少到近期访问最多的顺序(访问顺序)。这种映射很适合构建 LRU 缓存。<BR> * 对于访问顺序,为 true;对于插入顺序,则为 false * * 插入顺序排序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。 * 访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。 * * 按照访问的次序来排序的含义:当调用LinkedHashMap的get(key)或者put(key, * value)时,碰巧key在map中被包含,那么LinkedHashMap会将该对象放在线性结构的最后。 * */ this.map = new LinkedHashMap<K, V>(0, 0.75f, true); } /** * 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; } } /** * 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; } /** * 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 || map.isEmpty()) { break; } // 移除头部元素,即不经常使用元素,经常使用的被重新排列到末尾去了 Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } } /** * 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; } /** * 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}. */ 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. 测量每个item的大小 */ 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. */ 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); } }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。