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