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 #entryRemoved}. 30 * 31 * <p>If a cache miss should be computed on demand for the corresponding keys, 32 * override {@link #create}. This simplifies the calling code, allowing it to 33 * assume a value will always be returned, even when there's a cache miss. 34 * 35 * <p>By default, the cache size is measured in the number of entries. Override 36 * {@link #sizeOf} to size the cache in different units. For example, this cache 37 * is limited to 4MiB of bitmaps: 38 * <pre> {@code 39 * int cacheSize = 4 * 1024 * 1024; // 4MiB 40 * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { 41 * protected int sizeOf(String key, Bitmap value) { 42 * return value.getByteCount(); 43 * } 44 * }}</pre> 45 * 46 * <p>This class is thread-safe. Perform multiple cache operations atomically by 47 * synchronizing on the cache: <pre> {@code 48 * synchronized (cache) { 49 * if (cache.get(key) == null) { 50 * cache.put(key, value); 51 * } 52 * }}</pre> 53 * 54 * <p>This class does not allow null to be used as a key or value. A return 55 * value of null from {@link #get}, {@link #put} or {@link #remove} is 56 * unambiguous: the key was not in the cache. 57 */ 58public class LruCache<K, V> { 59 private final LinkedHashMap<K, V> map; 60 61 /** Size of this cache in units. Not necessarily the number of elements. */ 62 private int size; 63 private int maxSize; 64 65 private int putCount; 66 private int createCount; 67 private int evictionCount; 68 private int hitCount; 69 private int missCount; 70 71 /** 72 * @param maxSize for caches that do not override {@link #sizeOf}, this is 73 * the maximum number of entries in the cache. For all other caches, 74 * this is the maximum sum of the sizes of the entries in this cache. 75 */ 76 public LruCache(int maxSize) { 77 if (maxSize <= 0) { 78 throw new IllegalArgumentException("maxSize <= 0"); 79 } 80 this.maxSize = maxSize; 81 this.map = new LinkedHashMap<K, V>(0, 0.75f, true); 82 } 83 84 /** 85 * Returns the value for {@code key} if it exists in the cache or can be 86 * created by {@code #create}. If a value was returned, it is moved to the 87 * head of the queue. This returns null if a value is not cached and cannot 88 * be created. 89 */ 90 public final V get(K key) { 91 if (key == null) { 92 throw new NullPointerException("key == null"); 93 } 94 95 V mapValue; 96 synchronized (this) { 97 mapValue = map.get(key); 98 if (mapValue != null) { 99 hitCount++; 100 return mapValue; 101 } 102 missCount++; 103 } 104 105 /* 106 * Attempt to create a value. This may take a long time, and the map 107 * may be different when create() returns. If a conflicting value was 108 * added to the map while create() was working, we leave that value in 109 * the map and release the created value. 110 */ 111 112 V createdValue = create(key); 113 if (createdValue == null) { 114 return null; 115 } 116 117 synchronized (this) { 118 createCount++; 119 mapValue = map.put(key, createdValue); 120 121 if (mapValue != null) { 122 // There was a conflict so undo that last put 123 map.put(key, mapValue); 124 } else { 125 size += safeSizeOf(key, createdValue); 126 } 127 } 128 129 if (mapValue != null) { 130 entryRemoved(false, key, createdValue, mapValue); 131 return mapValue; 132 } else { 133 trimToSize(maxSize); 134 return createdValue; 135 } 136 } 137 138 /** 139 * Caches {@code value} for {@code key}. The value is moved to the head of 140 * the queue. 141 * 142 * @return the previous value mapped by {@code key}. 143 */ 144 public final V put(K key, V value) { 145 if (key == null || value == null) { 146 throw new NullPointerException("key == null || value == null"); 147 } 148 149 V previous; 150 synchronized (this) { 151 putCount++; 152 size += safeSizeOf(key, value); 153 previous = map.put(key, value); 154 if (previous != null) { 155 size -= safeSizeOf(key, previous); 156 } 157 } 158 159 if (previous != null) { 160 entryRemoved(false, key, previous, value); 161 } 162 163 trimToSize(maxSize); 164 return previous; 165 } 166 167 /** 168 * @param maxSize the maximum size of the cache before returning. May be -1 169 * to evict even 0-sized elements. 170 */ 171 private void trimToSize(int maxSize) { 172 while (true) { 173 K key; 174 V value; 175 synchronized (this) { 176 if (size < 0 || (map.isEmpty() && size != 0)) { 177 throw new IllegalStateException(getClass().getName() 178 + ".sizeOf() is reporting inconsistent results!"); 179 } 180 181 if (size <= maxSize) { 182 break; 183 } 184 185 Map.Entry<K, V> toEvict = map.eldest(); 186 if (toEvict == null) { 187 break; 188 } 189 190 key = toEvict.getKey(); 191 value = toEvict.getValue(); 192 map.remove(key); 193 size -= safeSizeOf(key, value); 194 evictionCount++; 195 } 196 197 entryRemoved(true, key, value, null); 198 } 199 } 200 201 /** 202 * Removes the entry for {@code key} if it exists. 203 * 204 * @return the previous value mapped by {@code key}. 205 */ 206 public final V remove(K key) { 207 if (key == null) { 208 throw new NullPointerException("key == null"); 209 } 210 211 V previous; 212 synchronized (this) { 213 previous = map.remove(key); 214 if (previous != null) { 215 size -= safeSizeOf(key, previous); 216 } 217 } 218 219 if (previous != null) { 220 entryRemoved(false, key, previous, null); 221 } 222 223 return previous; 224 } 225 226 /** 227 * Called for entries that have been evicted or removed. This method is 228 * invoked when a value is evicted to make space, removed by a call to 229 * {@link #remove}, or replaced by a call to {@link #put}. The default 230 * implementation does nothing. 231 * 232 * <p>The method is called without synchronization: other threads may 233 * access the cache while this method is executing. 234 * 235 * @param evicted true if the entry is being removed to make space, false 236 * if the removal was caused by a {@link #put} or {@link #remove}. 237 * @param newValue the new value for {@code key}, if it exists. If non-null, 238 * this removal was caused by a {@link #put}. Otherwise it was caused by 239 * an eviction or a {@link #remove}. 240 */ 241 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} 242 243 /** 244 * Called after a cache miss to compute a value for the corresponding key. 245 * Returns the computed value or null if no value can be computed. The 246 * default implementation returns null. 247 * 248 * <p>The method is called without synchronization: other threads may 249 * access the cache while this method is executing. 250 * 251 * <p>If a value for {@code key} exists in the cache when this method 252 * returns, the created value will be released with {@link #entryRemoved} 253 * and discarded. This can occur when multiple threads request the same key 254 * at the same time (causing multiple values to be created), or when one 255 * thread calls {@link #put} while another is creating a value for the same 256 * key. 257 */ 258 protected V create(K key) { 259 return null; 260 } 261 262 private int safeSizeOf(K key, V value) { 263 int result = sizeOf(key, value); 264 if (result < 0) { 265 throw new IllegalStateException("Negative size: " + key + "=" + value); 266 } 267 return result; 268 } 269 270 /** 271 * Returns the size of the entry for {@code key} and {@code value} in 272 * user-defined units. The default implementation returns 1 so that size 273 * is the number of entries and max size is the maximum number of entries. 274 * 275 * <p>An entry's size must not change while it is in the cache. 276 */ 277 protected int sizeOf(K key, V value) { 278 return 1; 279 } 280 281 /** 282 * Clear the cache, calling {@link #entryRemoved} on each removed entry. 283 */ 284 public final void evictAll() { 285 trimToSize(-1); // -1 will evict 0-sized elements 286 } 287 288 /** 289 * For caches that do not override {@link #sizeOf}, this returns the number 290 * of entries in the cache. For all other caches, this returns the sum of 291 * the sizes of the entries in this cache. 292 */ 293 public synchronized final int size() { 294 return size; 295 } 296 297 /** 298 * For caches that do not override {@link #sizeOf}, this returns the maximum 299 * number of entries in the cache. For all other caches, this returns the 300 * maximum sum of the sizes of the entries in this cache. 301 */ 302 public synchronized final int maxSize() { 303 return maxSize; 304 } 305 306 /** 307 * Returns the number of times {@link #get} returned a value that was 308 * already present in the cache. 309 */ 310 public synchronized final int hitCount() { 311 return hitCount; 312 } 313 314 /** 315 * Returns the number of times {@link #get} returned null or required a new 316 * value to be created. 317 */ 318 public synchronized final int missCount() { 319 return missCount; 320 } 321 322 /** 323 * Returns the number of times {@link #create(Object)} returned a value. 324 */ 325 public synchronized final int createCount() { 326 return createCount; 327 } 328 329 /** 330 * Returns the number of times {@link #put} was called. 331 */ 332 public synchronized final int putCount() { 333 return putCount; 334 } 335 336 /** 337 * Returns the number of values that have been evicted. 338 */ 339 public synchronized final int evictionCount() { 340 return evictionCount; 341 } 342 343 /** 344 * Returns a copy of the current contents of the cache, ordered from least 345 * recently accessed to most recently accessed. 346 */ 347 public synchronized final Map<K, V> snapshot() { 348 return new LinkedHashMap<K, V>(map); 349 } 350 351 @Override public synchronized final String toString() { 352 int accesses = hitCount + missCount; 353 int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; 354 return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", 355 maxSize, hitCount, missCount, hitPercent); 356 } 357} 358