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