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