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