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