blob: 3cbf727d7718aa3e3df8601fd189f826fb4c2b06 [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 Inwood4eb56ab2018-08-14 17:24:32 +010019import android.annotation.UnsupportedAppUsage;
Vinit Nayak3e737492019-07-24 13:03:15 -070020
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080021import java.util.LinkedHashMap;
22import 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,
Jesse Wilson7db1b402011-02-25 16:38:40 -080031 * override {@link #entryRemoved}.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080032 *
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
Jesse Wilson34895b02011-02-10 13:50:24 -080038 * {@link #sizeOf} to size the cache in different units. For example, this cache
39 * is limited to 4MiB of bitmaps:
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080040 * <pre> {@code
Jesse Wilson34895b02011-02-10 13:50:24 -080041 * 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);
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080053 * }
Jesse Wilson34895b02011-02-10 13:50:24 -080054 * }}</pre>
Jesse Wilson56b6ad32011-02-11 13:32:04 -080055 *
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.
Jesse Wilson02db1b62011-11-17 09:44:27 -050059 *
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.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080063 */
64public class LruCache<K, V> {
Mathew Inwood4eb56ab2018-08-14 17:24:32 +010065 @UnsupportedAppUsage
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080066 private final LinkedHashMap<K, V> map;
67
68 /** Size of this cache in units. Not necessarily the number of elements. */
69 private int size;
Jesse Wilson9b5a9352011-02-10 11:19:09 -080070 private int maxSize;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080071
72 private int putCount;
73 private int createCount;
74 private int evictionCount;
75 private int hitCount;
76 private int missCount;
77
78 /**
79 * @param maxSize for caches that do not override {@link #sizeOf}, this is
80 * the maximum number of entries in the cache. For all other caches,
81 * this is the maximum sum of the sizes of the entries in this cache.
82 */
83 public LruCache(int maxSize) {
84 if (maxSize <= 0) {
85 throw new IllegalArgumentException("maxSize <= 0");
86 }
87 this.maxSize = maxSize;
88 this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
89 }
90
91 /**
Jeff Browne5360fb2011-10-31 17:48:13 -070092 * Sets the size of the cache.
Jeff Browne5360fb2011-10-31 17:48:13 -070093 *
Narayan Kamatha1226a72014-02-20 10:27:55 +000094 * @param maxSize The new maximum size.
Jeff Browne5360fb2011-10-31 17:48:13 -070095 */
96 public void resize(int maxSize) {
97 if (maxSize <= 0) {
98 throw new IllegalArgumentException("maxSize <= 0");
99 }
100
101 synchronized (this) {
102 this.maxSize = maxSize;
103 }
104 trimToSize(maxSize);
105 }
106
107 /**
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800108 * Returns the value for {@code key} if it exists in the cache or can be
109 * created by {@code #create}. If a value was returned, it is moved to the
110 * head of the queue. This returns null if a value is not cached and cannot
111 * be created.
112 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800113 public final V get(K key) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800114 if (key == null) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800115 throw new NullPointerException("key == null");
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800116 }
117
Jesse Wilson7db1b402011-02-25 16:38:40 -0800118 V mapValue;
119 synchronized (this) {
120 mapValue = map.get(key);
121 if (mapValue != null) {
122 hitCount++;
123 return mapValue;
124 }
125 missCount++;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800126 }
127
Jesse Wilson7db1b402011-02-25 16:38:40 -0800128 /*
129 * Attempt to create a value. This may take a long time, and the map
130 * may be different when create() returns. If a conflicting value was
131 * added to the map while create() was working, we leave that value in
132 * the map and release the created value.
133 */
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800134
Jesse Wilson7db1b402011-02-25 16:38:40 -0800135 V createdValue = create(key);
136 if (createdValue == null) {
137 return null;
138 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800139
Jesse Wilson7db1b402011-02-25 16:38:40 -0800140 synchronized (this) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800141 createCount++;
Jesse Wilson7db1b402011-02-25 16:38:40 -0800142 mapValue = map.put(key, createdValue);
143
144 if (mapValue != null) {
145 // There was a conflict so undo that last put
146 map.put(key, mapValue);
147 } else {
148 size += safeSizeOf(key, createdValue);
149 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800150 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800151
152 if (mapValue != null) {
153 entryRemoved(false, key, createdValue, mapValue);
154 return mapValue;
155 } else {
156 trimToSize(maxSize);
157 return createdValue;
158 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800159 }
160
161 /**
162 * Caches {@code value} for {@code key}. The value is moved to the head of
163 * the queue.
164 *
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800165 * @return the previous value mapped by {@code key}.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800166 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800167 public final V put(K key, V value) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800168 if (key == null || value == null) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800169 throw new NullPointerException("key == null || value == null");
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800170 }
171
Jesse Wilson7db1b402011-02-25 16:38:40 -0800172 V previous;
173 synchronized (this) {
174 putCount++;
175 size += safeSizeOf(key, value);
176 previous = map.put(key, value);
177 if (previous != null) {
178 size -= safeSizeOf(key, previous);
179 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800180 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800181
182 if (previous != null) {
183 entryRemoved(false, key, previous, value);
184 }
185
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800186 trimToSize(maxSize);
187 return previous;
188 }
189
Jesse Wilson7db1b402011-02-25 16:38:40 -0800190 /**
Jeff Sharkeyd96b5852012-07-27 17:00:24 -0700191 * Remove the eldest entries until the total of remaining entries is at or
192 * below the requested size.
193 *
Jesse Wilson7db1b402011-02-25 16:38:40 -0800194 * @param maxSize the maximum size of the cache before returning. May be -1
Jeff Sharkeyd96b5852012-07-27 17:00:24 -0700195 * to evict even 0-sized elements.
Jesse Wilson7db1b402011-02-25 16:38:40 -0800196 */
Jeff Sharkeyd96b5852012-07-27 17:00:24 -0700197 public void trimToSize(int maxSize) {
Jesse Wilson7db1b402011-02-25 16:38:40 -0800198 while (true) {
199 K key;
200 V value;
201 synchronized (this) {
202 if (size < 0 || (map.isEmpty() && size != 0)) {
203 throw new IllegalStateException(getClass().getName()
204 + ".sizeOf() is reporting inconsistent results!");
205 }
206
207 if (size <= maxSize) {
208 break;
209 }
210
211 Map.Entry<K, V> toEvict = map.eldest();
212 if (toEvict == null) {
213 break;
214 }
215
216 key = toEvict.getKey();
217 value = toEvict.getValue();
218 map.remove(key);
219 size -= safeSizeOf(key, value);
220 evictionCount++;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800221 }
222
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800223 entryRemoved(true, key, value, null);
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800224 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800225 }
226
227 /**
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800228 * Removes the entry for {@code key} if it exists.
229 *
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800230 * @return the previous value mapped by {@code key}.
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800231 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800232 public final V remove(K key) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800233 if (key == null) {
234 throw new NullPointerException("key == null");
235 }
236
Jesse Wilson7db1b402011-02-25 16:38:40 -0800237 V previous;
238 synchronized (this) {
239 previous = map.remove(key);
240 if (previous != null) {
241 size -= safeSizeOf(key, previous);
242 }
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800243 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800244
245 if (previous != null) {
246 entryRemoved(false, key, previous, null);
247 }
248
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800249 return previous;
250 }
251
252 /**
Jesse Wilson7db1b402011-02-25 16:38:40 -0800253 * Called for entries that have been evicted or removed. This method is
254 * invoked when a value is evicted to make space, removed by a call to
255 * {@link #remove}, or replaced by a call to {@link #put}. The default
256 * implementation does nothing.
257 *
258 * <p>The method is called without synchronization: other threads may
259 * access the cache while this method is executing.
260 *
261 * @param evicted true if the entry is being removed to make space, false
262 * if the removal was caused by a {@link #put} or {@link #remove}.
263 * @param newValue the new value for {@code key}, if it exists. If non-null,
Vinit Nayak3e737492019-07-24 13:03:15 -0700264 * this removal was caused by a {@link #put} or a {@link #get}. Otherwise it was caused by
Jesse Wilson7db1b402011-02-25 16:38:40 -0800265 * an eviction or a {@link #remove}.
266 */
267 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800268
269 /**
270 * Called after a cache miss to compute a value for the corresponding key.
271 * Returns the computed value or null if no value can be computed. The
272 * default implementation returns null.
Jesse Wilson7db1b402011-02-25 16:38:40 -0800273 *
274 * <p>The method is called without synchronization: other threads may
275 * access the cache while this method is executing.
276 *
277 * <p>If a value for {@code key} exists in the cache when this method
278 * returns, the created value will be released with {@link #entryRemoved}
279 * and discarded. This can occur when multiple threads request the same key
280 * at the same time (causing multiple values to be created), or when one
281 * thread calls {@link #put} while another is creating a value for the same
282 * key.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800283 */
284 protected V create(K key) {
285 return null;
286 }
287
288 private int safeSizeOf(K key, V value) {
289 int result = sizeOf(key, value);
290 if (result < 0) {
291 throw new IllegalStateException("Negative size: " + key + "=" + value);
292 }
293 return result;
294 }
295
296 /**
297 * Returns the size of the entry for {@code key} and {@code value} in
298 * user-defined units. The default implementation returns 1 so that size
299 * is the number of entries and max size is the maximum number of entries.
300 *
301 * <p>An entry's size must not change while it is in the cache.
302 */
303 protected int sizeOf(K key, V value) {
304 return 1;
305 }
306
307 /**
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800308 * Clear the cache, calling {@link #entryRemoved} on each removed entry.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800309 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800310 public final void evictAll() {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800311 trimToSize(-1); // -1 will evict 0-sized elements
312 }
313
314 /**
Jesse Wilson9b5a9352011-02-10 11:19:09 -0800315 * For caches that do not override {@link #sizeOf}, this returns the number
316 * of entries in the cache. For all other caches, this returns the sum of
317 * the sizes of the entries in this cache.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800318 */
319 public synchronized final int size() {
320 return size;
321 }
322
323 /**
Jesse Wilson9b5a9352011-02-10 11:19:09 -0800324 * For caches that do not override {@link #sizeOf}, this returns the maximum
325 * number of entries in the cache. For all other caches, this returns the
326 * maximum sum of the sizes of the entries in this cache.
327 */
328 public synchronized final int maxSize() {
329 return maxSize;
330 }
331
332 /**
Jesse Wilsona460c182011-03-16 14:57:01 -0700333 * Returns the number of times {@link #get} returned a value that was
334 * already present in the cache.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800335 */
336 public synchronized final int hitCount() {
337 return hitCount;
338 }
339
340 /**
341 * Returns the number of times {@link #get} returned null or required a new
342 * value to be created.
343 */
344 public synchronized final int missCount() {
345 return missCount;
346 }
347
348 /**
349 * Returns the number of times {@link #create(Object)} returned a value.
350 */
351 public synchronized final int createCount() {
352 return createCount;
353 }
354
355 /**
356 * Returns the number of times {@link #put} was called.
357 */
358 public synchronized final int putCount() {
359 return putCount;
360 }
361
362 /**
363 * Returns the number of values that have been evicted.
364 */
365 public synchronized final int evictionCount() {
366 return evictionCount;
367 }
368
369 /**
370 * Returns a copy of the current contents of the cache, ordered from least
371 * recently accessed to most recently accessed.
372 */
373 public synchronized final Map<K, V> snapshot() {
374 return new LinkedHashMap<K, V>(map);
375 }
376
377 @Override public synchronized final String toString() {
378 int accesses = hitCount + missCount;
379 int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
380 return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
381 maxSize, hitCount, missCount, hitPercent);
382 }
383}