LruCache 缓存
内存缓存(LruCache):
系统提供的LruCache类是非常适合用作缓存Bitmap任务的,
它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。
注意:以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,
然而现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。
http://developer.android.com/reference/android/util/LruCache.html
/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.util; import java.util.LinkedHashMap; import java.util.Map; /** * Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。 * 当cache已满的时候加入新的item时,在队列尾部的item会被回收。 * 如果你cache的某个值需要明确释放,重写entryRemoved() * 如果key相对应的item丢掉啦,重写create().这简化了调用代码,即使丢失了也总会返回 * 默认cache大小是测量的item的数量,重写sizeof计算不同item的 * 大小。 * 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(); * } * }} * 不允许key或者value为null * 当get(),put(),remove()返回值为null时,key相应的项不在cache中 */ 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; //put的次数 private int createCount;//create的次数 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); // LinkedHashMap的初始化放在构造器中, //这里将LinkedHashMap的accessOrder设置为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. * 通过key返回相应的item,或者创建返回相应的item。相应的item会移动到队列的头部, * 如果item的value没有被cache或者不能被创建,则返回null。 */ public final V get(K key) { if (key == null) { //不允许空键 throw new NullPointerException("key == null"); } V mapValue; synchronized (this) { mapValue = map.get(key);//调用LinkedHashMap的get方法 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. * 如果丢失了就试图创建一个item */ V createdValue = create(key); if (createdValue == null) { return null; } synchronized (this) { createCount++; //如果创建成功,那么create次数加1 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);//统计放入的值的大小,然后增加size的记录值 previous = map.put(key, value);//把新值放入缓存map中,然后获得旧值 if (previous != null) { //旧值不为空,由于替换了旧值,所以需要把缓存数据总大小减去这个旧值的大小 size -= safeSizeOf(key, previous); } } //还要调用entryRemoved()方法来让子类去处理不用的旧值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. 把最少访问的老数据删除,直到总数据大小在上限的范围之内. 数据上限. 值可能是-1,这样就会删除所有的缓存数据. */ public void trimToSize(int maxSize) { while (true) { //不断删除linkedHashMap头部entry,也就是最近最少访问的条目,直到size小于最大容量 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(); //获取最少访问的entry if (toEvict == null) { break; } key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key);//删除最少访问的entry 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);//调用LinkedHashMap的remove方法 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); } }
总结:
1.LruCache封装了LinkedHashMap,提供了LRU缓存的功能;
2.LruCache通过trimToSize方法自动删除最近最少访问的键值对;
3.LruCache不允许空键值;
4.LruCache线程安全;
5.继承LruCache时,必须要复写sizeof方法,用于计算每个条目的大小。