LruCache.java revision 543146a82b87b33973743cbd1880e899ebba30f5
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.util; 18 19import java.util.LinkedHashMap; 20import java.util.Map; 21 22/** 23 * A cache that holds strong references to a limited number of values. Each time 24 * a value is accessed, it is moved to the head of a queue. When a value is 25 * added to a full cache, the value at the end of that queue is evicted and may 26 * become eligible for garbage collection. 27 * 28 * <p>If your cached values hold resources that need to be explicitly released, 29 * override {@link #entryEvicted}. This method is only invoked when values are 30 * evicted. Values replaced by calls to {@link #put} must be released manually. 31 * 32 * <p>If a cache miss should be computed on demand for the corresponding keys, 33 * override {@link #create}. This simplifies the calling code, allowing it to 34 * assume a value will always be returned, even when there's a cache miss. 35 * 36 * <p>By default, the cache size is measured in the number of entries. Override 37 * {@link #sizeOf} to size the cache in different units. For, this cache is 38 * limited to 4MiB of bitmaps: 39 * <pre> {@code 40 * int cacheSize = 4 * 1024 * 1024; // 4MiB 41 * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { 42 * protected int sizeOf(String key, Bitmap value) { 43 * return value.getByteCount(); 44 * } 45 * }}</pre> 46 */ 47public class LruCache<K, V> { 48 private final LinkedHashMap<K, V> map; 49 50 /** Size of this cache in units. Not necessarily the number of elements. */ 51 private int size; 52 private final int maxSize; 53 54 private int putCount; 55 private int createCount; 56 private int evictionCount; 57 private int hitCount; 58 private int missCount; 59 60 /** 61 * @param maxSize for caches that do not override {@link #sizeOf}, this is 62 * the maximum number of entries in the cache. For all other caches, 63 * this is the maximum sum of the sizes of the entries in this cache. 64 */ 65 public LruCache(int maxSize) { 66 if (maxSize <= 0) { 67 throw new IllegalArgumentException("maxSize <= 0"); 68 } 69 this.maxSize = maxSize; 70 this.map = new LinkedHashMap<K, V>(0, 0.75f, true); 71 } 72 73 /** 74 * Returns the value for {@code key} if it exists in the cache or can be 75 * created by {@code #create}. If a value was returned, it is moved to the 76 * head of the queue. This returns null if a value is not cached and cannot 77 * be created. 78 */ 79 public synchronized final V get(K key) { 80 if (key == null) { 81 throw new NullPointerException(); 82 } 83 84 V result = map.get(key); 85 if (result != null) { 86 hitCount++; 87 return result; 88 } 89 90 missCount++; 91 92 // TODO: release the lock while calling this potentially slow user code 93 result = create(key); 94 95 if (result != null) { 96 createCount++; 97 size += safeSizeOf(key, result); 98 map.put(key, result); 99 trimToSize(maxSize); 100 } 101 return result; 102 } 103 104 /** 105 * Caches {@code value} for {@code key}. The value is moved to the head of 106 * the queue. 107 * 108 * @return the previous value mapped by {@code key}. Although that entry is 109 * no longer cached, it has not been passed to {@link #entryEvicted}. 110 */ 111 public synchronized final V put(K key, V value) { 112 if (key == null || value == null) { 113 throw new NullPointerException(); 114 } 115 116 putCount++; 117 size += safeSizeOf(key, value); 118 V previous = map.put(key, value); 119 if (previous != null) { 120 size -= safeSizeOf(key, previous); 121 } 122 trimToSize(maxSize); 123 return previous; 124 } 125 126 private void trimToSize(int maxSize) { 127 while (size > maxSize) { 128 Map.Entry<K, V> toEvict = map.eldest(); 129 if (toEvict == null) { 130 break; // map is empty; if size is not 0 then throw an error below 131 } 132 133 K key = toEvict.getKey(); 134 V value = toEvict.getValue(); 135 map.remove(key); 136 size -= safeSizeOf(key, value); 137 evictionCount++; 138 139 // TODO: release the lock while calling this potentially slow user code 140 entryEvicted(key, value); 141 } 142 143 if (size < 0 || (map.isEmpty() && size != 0)) { 144 throw new IllegalStateException(getClass().getName() 145 + ".sizeOf() is reporting inconsistent results!"); 146 } 147 } 148 149 /** 150 * Called for entries that have reached the tail of the least recently used 151 * queue and are be removed. The default implementation does nothing. 152 */ 153 protected void entryEvicted(K key, V value) {} 154 155 /** 156 * Called after a cache miss to compute a value for the corresponding key. 157 * Returns the computed value or null if no value can be computed. The 158 * default implementation returns null. 159 */ 160 protected V create(K key) { 161 return null; 162 } 163 164 private int safeSizeOf(K key, V value) { 165 int result = sizeOf(key, value); 166 if (result < 0) { 167 throw new IllegalStateException("Negative size: " + key + "=" + value); 168 } 169 return result; 170 } 171 172 /** 173 * Returns the size of the entry for {@code key} and {@code value} in 174 * user-defined units. The default implementation returns 1 so that size 175 * is the number of entries and max size is the maximum number of entries. 176 * 177 * <p>An entry's size must not change while it is in the cache. 178 */ 179 protected int sizeOf(K key, V value) { 180 return 1; 181 } 182 183 /** 184 * Clear the cache, calling {@link #entryEvicted} on each removed entry. 185 */ 186 public synchronized final void evictAll() { 187 trimToSize(-1); // -1 will evict 0-sized elements 188 } 189 190 /** 191 * For caches that do not override {@link #sizeOf}, this is the number of 192 * entries in the cache. For all other caches, this is the sum of the sizes 193 * of the entries in this cache. 194 */ 195 public synchronized final int size() { 196 return size; 197 } 198 199 /** 200 * Returns the number of times {@link #get} returned a value. 201 */ 202 public synchronized final int hitCount() { 203 return hitCount; 204 } 205 206 /** 207 * Returns the number of times {@link #get} returned null or required a new 208 * value to be created. 209 */ 210 public synchronized final int missCount() { 211 return missCount; 212 } 213 214 /** 215 * Returns the number of times {@link #create(Object)} returned a value. 216 */ 217 public synchronized final int createCount() { 218 return createCount; 219 } 220 221 /** 222 * Returns the number of times {@link #put} was called. 223 */ 224 public synchronized final int putCount() { 225 return putCount; 226 } 227 228 /** 229 * Returns the number of values that have been evicted. 230 */ 231 public synchronized final int evictionCount() { 232 return evictionCount; 233 } 234 235 /** 236 * Returns a copy of the current contents of the cache, ordered from least 237 * recently accessed to most recently accessed. 238 */ 239 public synchronized final Map<K, V> snapshot() { 240 return new LinkedHashMap<K, V>(map); 241 } 242 243 @Override public synchronized final String toString() { 244 int accesses = hitCount + missCount; 245 int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; 246 return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", 247 maxSize, hitCount, missCount, hitPercent); 248 } 249} 250