一.注释
LRUCache的原理,基本都在注释里面描述清楚了。
/** * 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. */
1.每次一个元素被访问,它会被move到队列的head位置。当某个元素加入到已经full的池里面,最久未使用的一个元素会被delete。
2.如果元素需要做特殊的释放操作,请重载entryRemoved
3.如果某个key值,没有对应的初始值,可以通过create方法提供默认值。
4.创建一个LRUCache需要复写sizeof方法。
5.这个类是线程安全的。
二:源代码分析:
1.变量
private final LinkedHashMap<K, V> map; /** Size of this cache in units. Not necessarily the number of elements. */ private int size; //count private int maxSize; //容量 private int putCount; private int createCount; private int evictionCount; //delete count private int hitCount; //命中 count private int missCount; //未命中 count
2.构造函数
/** * @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); }
这段代码最关键的是,new了一个LinkedHashMap,这个hashmap是可以根据访问先后次序,来控制顺序的。
LinkedHashMap:第一个参数表示初始数据是0个。
0.75f 是经典的加载因子的数值。
第三个参数true表示,以访问的先后顺序来排序。也就是做到了,容量满了以后,删除最久未访问的数据。
3.resize
/** * Sets the size of the cache. * * @param maxSize The new maximum size. */ public void resize(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } synchronized (this) { this.maxSize = maxSize; } trimToSize(maxSize); }
maxsize就是容量,也就是说,队列的最大长度,当full的时候,最后一个会被delete。
resize 就是调用trimToSize
4.trimToSize
/** * 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); } }
最外面的是while true 死循环。 然后开始判读size的值。1)获取最后的一个entry,拿到后,getkey & value。
2)map remove掉它。3)然后是获取这个entry占用的大小(不一定是1)。size 减去这个值。 4)evictionCount++
5)entryRemoved 之前说过,就是可以
5.get
/** * 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 /** * 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; } }map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { trimToSize(maxSize); return createdValue; } }
1)从map中获取元素,2)没有的话,就创建一个 3)由于存在多线程问题,可能已经好了一个。所以要删除最新创建的这个,或者增加size的值
4)如果create的值不需要,就释放create的值,or trimToSize
6.put
/** * 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; }
map 存在一个多线程 create,put的问题。所以在put的时候,可能由其他线程已经存在了oldvalue值。所以根据
previous = map.put(key, value);
这个特性,判断previous的值,来确认是否是重复put的问题。这里关键是牵涉到size的大小问题。
其他方法,注释已经写的很清楚,不难理解。
总结:
- LruCache 封装了 LinkedHashMap,提供了 LRU 缓存的功能;
- LruCache 通过 trimToSize 方法自动删除最近最少访问的键值对;
- LruCache 不允许空键值;
- LruCache 线程安全;
- LruCache 的源码在不同版本中不一样,需要区分
- 继承 LruCache 时,必须要复写 sizeOf 方法,用于计算每个条目的大小。