blob: f04e7cbc9e8fca8528626c969e06f1cd4f3daa61 [file] [log] [blame]
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -08001/*
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
17package android.util;
18
Mathew Inwoodb4075682018-08-14 17:32:44 +010019import android.annotation.UnsupportedAppUsage;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080020import java.util.LinkedHashMap;
21import 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,
Jesse Wilson7db1b402011-02-25 16:38:40 -080030 * override {@link #entryRemoved}.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080031 *
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
Jesse Wilson34895b02011-02-10 13:50:24 -080037 * {@link #sizeOf} to size the cache in different units. For example, this cache
38 * is limited to 4MiB of bitmaps:
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080039 * <pre> {@code
Jesse Wilson34895b02011-02-10 13:50:24 -080040 * 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);
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080052 * }
Jesse Wilson34895b02011-02-10 13:50:24 -080053 * }}</pre>
Jesse Wilson56b6ad32011-02-11 13:32:04 -080054 *
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.
Jesse Wilson02db1b62011-11-17 09:44:27 -050058 *
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.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080062 */
63public class LruCache<K, V> {
Mathew Inwoodb4075682018-08-14 17:32:44 +010064 @UnsupportedAppUsage
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080065 private final LinkedHashMap<K, V> map;
66
67 /** Size of this cache in units. Not necessarily the number of elements. */
68 private int size;
Jesse Wilson9b5a9352011-02-10 11:19:09 -080069 private int maxSize;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080070
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 */
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 /**
Jeff Browne5360fb2011-10-31 17:48:13 -070091 * Sets the size of the cache.
Jeff Browne5360fb2011-10-31 17:48:13 -070092 *
Narayan Kamatha1226a72014-02-20 10:27:55 +000093 * @param maxSize The new maximum size.
Jeff Browne5360fb2011-10-31 17:48:13 -070094 */
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 /**
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800107 * 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 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800112 public final V get(K key) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800113 if (key == null) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800114 throw new NullPointerException("key == null");
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800115 }
116
Jesse Wilson7db1b402011-02-25 16:38:40 -0800117 V mapValue;
118 synchronized (this) {
119 mapValue = map.get(key);
120 if (mapValue != null) {
121 hitCount++;
122 return mapValue;
123 }
124 missCount++;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800125 }
126
Jesse Wilson7db1b402011-02-25 16:38:40 -0800127 /*
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 */
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800133
Jesse Wilson7db1b402011-02-25 16:38:40 -0800134 V createdValue = create(key);
135 if (createdValue == null) {
136 return null;
137 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800138
Jesse Wilson7db1b402011-02-25 16:38:40 -0800139 synchronized (this) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800140 createCount++;
Jesse Wilson7db1b402011-02-25 16:38:40 -0800141 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 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800149 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800150
151 if (mapValue != null) {
152 entryRemoved(false, key, createdValue, mapValue);
153 return mapValue;
154 } else {
155 trimToSize(maxSize);
156 return createdValue;
157 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800158 }
159
160 /**
161 * Caches {@code value} for {@code key}. The value is moved to the head of
162 * the queue.
163 *
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800164 * @return the previous value mapped by {@code key}.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800165 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800166 public final V put(K key, V value) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800167 if (key == null || value == null) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800168 throw new NullPointerException("key == null || value == null");
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800169 }
170
Jesse Wilson7db1b402011-02-25 16:38:40 -0800171 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 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800179 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800180
181 if (previous != null) {
182 entryRemoved(false, key, previous, value);
183 }
184
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800185 trimToSize(maxSize);
186 return previous;
187 }
188
Jesse Wilson7db1b402011-02-25 16:38:40 -0800189 /**
Jeff Sharkeyd96b5852012-07-27 17:00:24 -0700190 * Remove the eldest entries until the total of remaining entries is at or
191 * below the requested size.
192 *
Jesse Wilson7db1b402011-02-25 16:38:40 -0800193 * @param maxSize the maximum size of the cache before returning. May be -1
Jeff Sharkeyd96b5852012-07-27 17:00:24 -0700194 * to evict even 0-sized elements.
Jesse Wilson7db1b402011-02-25 16:38:40 -0800195 */
Jeff Sharkeyd96b5852012-07-27 17:00:24 -0700196 public void trimToSize(int maxSize) {
Jesse Wilson7db1b402011-02-25 16:38:40 -0800197 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++;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800220 }
221
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800222 entryRemoved(true, key, value, null);
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800223 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800224 }
225
226 /**
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800227 * Removes the entry for {@code key} if it exists.
228 *
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800229 * @return the previous value mapped by {@code key}.
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800230 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800231 public final V remove(K key) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800232 if (key == null) {
233 throw new NullPointerException("key == null");
234 }
235
Jesse Wilson7db1b402011-02-25 16:38:40 -0800236 V previous;
237 synchronized (this) {
238 previous = map.remove(key);
239 if (previous != null) {
240 size -= safeSizeOf(key, previous);
241 }
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800242 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800243
244 if (previous != null) {
245 entryRemoved(false, key, previous, null);
246 }
247
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800248 return previous;
249 }
250
251 /**
Jesse Wilson7db1b402011-02-25 16:38:40 -0800252 * 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 */
266 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800267
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.
Jesse Wilson7db1b402011-02-25 16:38:40 -0800272 *
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.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800282 */
283 protected V create(K key) {
284 return null;
285 }
286
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 */
302 protected int sizeOf(K key, V value) {
303 return 1;
304 }
305
306 /**
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800307 * Clear the cache, calling {@link #entryRemoved} on each removed entry.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800308 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800309 public final void evictAll() {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800310 trimToSize(-1); // -1 will evict 0-sized elements
311 }
312
313 /**
Jesse Wilson9b5a9352011-02-10 11:19:09 -0800314 * 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.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800317 */
318 public synchronized final int size() {
319 return size;
320 }
321
322 /**
Jesse Wilson9b5a9352011-02-10 11:19:09 -0800323 * 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 */
327 public synchronized final int maxSize() {
328 return maxSize;
329 }
330
331 /**
Jesse Wilsona460c182011-03-16 14:57:01 -0700332 * Returns the number of times {@link #get} returned a value that was
333 * already present in the cache.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800334 */
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 */
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 */
350 public synchronized final int createCount() {
351 return createCount;
352 }
353
354 /**
355 * Returns the number of times {@link #put} was called.
356 */
357 public synchronized final int putCount() {
358 return putCount;
359 }
360
361 /**
362 * Returns the number of values that have been evicted.
363 */
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 */
372 public synchronized final Map<K, V> snapshot() {
373 return new LinkedHashMap<K, V>(map);
374 }
375
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}