【Android 开发】深入理解内存缓存类LruCache:源码分析
来源:互联网 发布:cocostudio 1.6 mac 编辑:程序博客网 时间:2024/05/21 04:02
1. 原理
LruCache是Android API实现的用于内存缓存的类,LRU即“Leatestrecent used”的缩写,典型应用是集中展示多个BitMap。我在《现代操作系统》一书中第3章第4节的“页面置换算法”中看到过该算法,比较简单。
LruCache内部依赖于队列(LinkedHashMap<K,V>方式实现了队列)。最近使用的对象位于队尾,最不常使用的位于队头。每一个对象入队,都计算整个队列是否超过最大内存空间,如果超过,就从队头将对象出队,直到队列空间小于最大存储空间。
LruCache是线程安全的。
2. 源码分析
2.1初始化
首先看LruCache的8个成员变量,其中map就是队列的一种实现,LruCache的所有操作都是围绕map的存取进行的。
private finalLinkedHashMap<K, V> map; //链表存储的Key-Value /** Size of this cache in units. Not necessarily the number of elements.*/ private int size; //按用户指定的单位(Byte、MByte……)计算的累计缓存。 private int maxSize; //最大缓存 private int putCount; //入队的对象,计数 private int createCount; //通过key没有查找到value,然后根据key生成了value,计数 private int evictionCount; //由于加入新的对象超过缓存区大小而被踢出的对象个数 private int hitCount; //通过key查找到了value,计数 private int miscount; //通过key没有查找到value,计数
LruCache有唯一的构造函数,将map进行初始化。初始化时map的容量为0,负载因子为0.75。负载因子是哈希表的一个术语,表明要存储的对象个数与map容量的最大比值。负载因子0.75,表明每个map的位置可以存储0.75个对象。负载因子小于1,可以减少哈希表的冲突现象。
/** * @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); }
初始化时,必须同时指定以何种单位来计算LruCache的内存大小,默认是以key-value的个数为单位,显然不是内存大小。
/** * 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; }
例如,下面就是一种初始化方式,以字节为单位计算缓存。
int cacheSize = 4 * 1024 * 1024; // 4MiB LruCache bitmapCache = new LruCache(cacheSize) { protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); //除以1024可以换位以MB为单位计算缓存 }}
除了在初始化时指定缓存大小,LruCache也可以在初始化之后指定缓存大小。
/** * Sets the size of the cache. * @param maxSize The new maximum size. * * @hide */ public void resize(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } synchronized (this) { this.maxSize = maxSize; } trimToSize(maxSize); //根据当前的缓存容量,调整map小于容量 }
2.2 Key-value入队
Key-value入队的过程主要是查看该key是否已经在map中存储有值(冲突检测),没有的话就直接添加。否则,就让新的value替换key对应的旧的value。
/** * 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); //加入的key是否已经有匹配 if (previous != null) { //有匹配 size -= safeSizeOf(key, previous); //容量减去之前能匹配key的对象 } } if (previous != null) { entryRemoved(false, key, previous, value); //去除之前的匹配的value } trimToSize(maxSize); //调整缓存 return previous;}
用来调整缓存大小的函数是trimToSize(maxSize),这里是判断当前缓存是否越界,如果越界就持续去除队头对象,直到满足条件。
/** * 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();//队头的key-value if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); }
另外在入队和调整队列大小的时候都用到了一个函数,即entryRemoved(true, key, value, null),这是一个空的用于继承的函数,这些踢出缓存的key-value如果要再进行处理,可以继承该方法。
/** * 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) {}
例如,对于BitMap来讲,暂时不必将这些从缓存中踢出的对象从内存中擦除,可以将这些缓冲区踢出的对象放到软引用集合中,生成新的BitMap时可以查找一下这个软引用队列是否有可用的旧的BitMap内存空间,有的话就将新的BitMap覆盖到旧的BitMap内存空间。这样,就节省了一次旧的BitMap的GC过程。例如,下面在初始化LruCache时就复写了该函数,将踢出缓存的对象放到了软引用集合中。
public class ImageCache { private LruCache<String, BitmapDrawable> mMemoryCache; private Set<SoftReference<Bitmap>> mReusableBitmaps;……省略无关内容…… mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) { /** * Notify the removed entry that is no longer being cached */ @Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) { if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { // The removed entry is a recycling drawable, so notify it // that it has been removed from the memory cache ((RecyclingBitmapDrawable) oldValue).setIsCached(false); } else { // The removed entry is a standard BitmapDrawable if (Utils.hasHoneycomb()) { // We're running on Honeycomb or later, so add the bitmap // to a SoftReference set for possible use with inBitmap later mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap())); } } } /** * Measure item size in kilobytes rather than units which is more practical * for a bitmap cache */ @Override protected int sizeOf(String key, BitmapDrawable value) { final int bitmapSize = getBitmapSize(value) / 1024; return bitmapSize == 0 ? 1 : bitmapSize; } }; }……省略无关内容……}
2.3Key-value查找
查找的过程主要考察没有找到的情况。LruCache提供了可继承的函数,看用户是否要根据key生成一个value。如果既没找到,用户也没有提供重写key生成value的函数,那就返回null。
/** * 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) { //查找到value hitCount++; //计数 return mapValue; } missCount++; //没有查找到value,计数 } /* * 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); //没有找到的情况,看用户是否根据key生成value if (createdValue == null) { //用户不生成value return null; } synchronized (this) { createCount++; //没有找到,用户根据key生成了value,计数 mapValue = map.put(key, createdValue); //将生成的key-value入队 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; }}其中,根据key生成value的过程如下,默认是null,可以继承覆写。
/** * 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; }
2.4缓存快照
可以获得缓存的拷贝。
/** * 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);<pre name="code" class="java"> }
附录-完整源码
package android.util;import java.util.LinkedHashMap;import java.util.Map;/** * A cache that holds strong references to a limited number of values. Each time * a value is accessed, it is moved to the head of a queue. When a value is * added to a full cache, the value at the end of that queue is evicted and may * become eligible for garbage collection. * * <p>If your cached values hold resources that need to be explicitly released, * override {@link #entryRemoved}. * * <p>If a cache miss should be computed on demand for the corresponding keys, * override {@link #create}. This simplifies the calling code, allowing it to * assume a value will always be returned, even when there's a cache miss. * * <p>By default, the cache size is measured in the number of entries. Override * {@link #sizeOf} to size the cache in different units. For example, this cache * is limited to 4MiB of bitmaps: * <pre> {@code * int cacheSize = 4 * 1024 * 1024; // 4MiB * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { * protected int sizeOf(String key, Bitmap value) { * return value.getByteCount(); * } * }}</pre> * * <p>This class is thread-safe. Perform multiple cache operations atomically by * synchronizing on the cache: <pre> {@code * synchronized (cache) { * if (cache.get(key) == null) { * cache.put(key, value); * } * }}</pre> * * <p>This class does not allow null to be used as a key or value. A return * value of null from {@link #get}, {@link #put} or {@link #remove} is * unambiguous: the key was not in the cache. * * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's * Support Package</a> for earlier releases. */public class LruCache<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; 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); } /** * Sets the size of the cache. * @param maxSize The new maximum size. * * @hide */ 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. */ 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) { 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); } } /** * 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. */ 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); }}
- 【Android 开发】深入理解内存缓存类LruCache:源码分析
- 【Android开发】深入理解硬盘缓存类DiskLruCache:源码分析
- Android LruCache 缓存 类 源码 注解 分析
- 《android framework常用api源码分析》之LruCache内存缓存
- android LruCache内存缓存源码解析
- Android内存缓存LruCache源码解析
- Android 源码系列之<八>从源码的角度深入理解缓存策略之LruCache
- Android缓存源码分析(DiskLruCache,LruCache)
- Android内存缓存LruCache
- Android 内存缓存 LruCache
- Android LruCache(内存缓存)
- Android LruCache(内存缓存)
- Android缓存-LruCache分析
- 内存缓存LruCache源码解析
- Android 源码解析-LruCache 缓存工具类
- Android内存缓存管理LruCache源码解析与示例
- android LRUCache源码分析
- Android LruCache 源码分析
- Swift_字符串
- C# 导出数据到Excel模板中
- hdu 1242 Rescue
- MariaDB Galera Cluster 部署
- C#导出EXCEL的几种方法
- 【Android 开发】深入理解内存缓存类LruCache:源码分析
- iOS设置textView的行间距
- Cloudera之旅 ☞ 【二】Spark-sql部署
- 类成员函数作为线程函数
- IOS中对于多个按钮,选中其中一个,其他按钮选中状态为NO
- MySQL性能优化的最佳20+条经验
- Swift_运算符_流程控制
- iOS中关于设置label字体样式
- 通过listview实现自定义TimePicker附带动画效果