blob: f1014a7ae1b19f13565e496af6ab064a4a20a222 [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
19import java.util.LinkedHashMap;
20import java.util.Map;
21
22/**
23 * A cache that holds strong references to a limited number of values. Each time
24 * a value is accessed, it is moved to the head of a queue. When a value is
25 * added to a full cache, the value at the end of that queue is evicted and may
26 * become eligible for garbage collection.
27 *
28 * <p>If your cached values hold resources that need to be explicitly released,
Jesse Wilson7db1b402011-02-25 16:38:40 -080029 * override {@link #entryRemoved}.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080030 *
31 * <p>If a cache miss should be computed on demand for the corresponding keys,
32 * override {@link #create}. This simplifies the calling code, allowing it to
33 * assume a value will always be returned, even when there's a cache miss.
34 *
35 * <p>By default, the cache size is measured in the number of entries. Override
Jesse Wilson34895b02011-02-10 13:50:24 -080036 * {@link #sizeOf} to size the cache in different units. For example, this cache
37 * is limited to 4MiB of bitmaps:
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080038 * <pre> {@code
Jesse Wilson34895b02011-02-10 13:50:24 -080039 * int cacheSize = 4 * 1024 * 1024; // 4MiB
40 * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
41 * protected int sizeOf(String key, Bitmap value) {
42 * return value.getByteCount();
43 * }
44 * }}</pre>
45 *
46 * <p>This class is thread-safe. Perform multiple cache operations atomically by
47 * synchronizing on the cache: <pre> {@code
48 * synchronized (cache) {
49 * if (cache.get(key) == null) {
50 * cache.put(key, value);
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080051 * }
Jesse Wilson34895b02011-02-10 13:50:24 -080052 * }}</pre>
Jesse Wilson56b6ad32011-02-11 13:32:04 -080053 *
54 * <p>This class does not allow null to be used as a key or value. A return
55 * value of null from {@link #get}, {@link #put} or {@link #remove} is
56 * unambiguous: the key was not in the cache.
Jesse Wilson02db1b62011-11-17 09:44:27 -050057 *
58 * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
59 * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
60 * Support Package</a> for earlier releases.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080061 */
62public class LruCache<K, V> {
63 private final LinkedHashMap<K, V> map;
64
65 /** Size of this cache in units. Not necessarily the number of elements. */
66 private int size;
Jesse Wilson9b5a9352011-02-10 11:19:09 -080067 private int maxSize;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080068
69 private int putCount;
70 private int createCount;
71 private int evictionCount;
72 private int hitCount;
73 private int missCount;
74
75 /**
76 * @param maxSize for caches that do not override {@link #sizeOf}, this is
77 * the maximum number of entries in the cache. For all other caches,
78 * this is the maximum sum of the sizes of the entries in this cache.
79 */
80 public LruCache(int maxSize) {
81 if (maxSize <= 0) {
82 throw new IllegalArgumentException("maxSize <= 0");
83 }
84 this.maxSize = maxSize;
85 this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
86 }
87
88 /**
89 * Returns the value for {@code key} if it exists in the cache or can be
90 * created by {@code #create}. If a value was returned, it is moved to the
91 * head of the queue. This returns null if a value is not cached and cannot
92 * be created.
93 */
Jesse Wilson7db1b402011-02-25 16:38:40 -080094 public final V get(K key) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080095 if (key == null) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -080096 throw new NullPointerException("key == null");
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080097 }
98
Jesse Wilson7db1b402011-02-25 16:38:40 -080099 V mapValue;
100 synchronized (this) {
101 mapValue = map.get(key);
102 if (mapValue != null) {
103 hitCount++;
104 return mapValue;
105 }
106 missCount++;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800107 }
108
Jesse Wilson7db1b402011-02-25 16:38:40 -0800109 /*
110 * Attempt to create a value. This may take a long time, and the map
111 * may be different when create() returns. If a conflicting value was
112 * added to the map while create() was working, we leave that value in
113 * the map and release the created value.
114 */
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800115
Jesse Wilson7db1b402011-02-25 16:38:40 -0800116 V createdValue = create(key);
117 if (createdValue == null) {
118 return null;
119 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800120
Jesse Wilson7db1b402011-02-25 16:38:40 -0800121 synchronized (this) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800122 createCount++;
Jesse Wilson7db1b402011-02-25 16:38:40 -0800123 mapValue = map.put(key, createdValue);
124
125 if (mapValue != null) {
126 // There was a conflict so undo that last put
127 map.put(key, mapValue);
128 } else {
129 size += safeSizeOf(key, createdValue);
130 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800131 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800132
133 if (mapValue != null) {
134 entryRemoved(false, key, createdValue, mapValue);
135 return mapValue;
136 } else {
137 trimToSize(maxSize);
138 return createdValue;
139 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800140 }
141
142 /**
143 * Caches {@code value} for {@code key}. The value is moved to the head of
144 * the queue.
145 *
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800146 * @return the previous value mapped by {@code key}.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800147 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800148 public final V put(K key, V value) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800149 if (key == null || value == null) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800150 throw new NullPointerException("key == null || value == null");
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800151 }
152
Jesse Wilson7db1b402011-02-25 16:38:40 -0800153 V previous;
154 synchronized (this) {
155 putCount++;
156 size += safeSizeOf(key, value);
157 previous = map.put(key, value);
158 if (previous != null) {
159 size -= safeSizeOf(key, previous);
160 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800161 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800162
163 if (previous != null) {
164 entryRemoved(false, key, previous, value);
165 }
166
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800167 trimToSize(maxSize);
168 return previous;
169 }
170
Jesse Wilson7db1b402011-02-25 16:38:40 -0800171 /**
172 * @param maxSize the maximum size of the cache before returning. May be -1
173 * to evict even 0-sized elements.
174 */
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800175 private void trimToSize(int maxSize) {
Jesse Wilson7db1b402011-02-25 16:38:40 -0800176 while (true) {
177 K key;
178 V value;
179 synchronized (this) {
180 if (size < 0 || (map.isEmpty() && size != 0)) {
181 throw new IllegalStateException(getClass().getName()
182 + ".sizeOf() is reporting inconsistent results!");
183 }
184
185 if (size <= maxSize) {
186 break;
187 }
188
189 Map.Entry<K, V> toEvict = map.eldest();
190 if (toEvict == null) {
191 break;
192 }
193
194 key = toEvict.getKey();
195 value = toEvict.getValue();
196 map.remove(key);
197 size -= safeSizeOf(key, value);
198 evictionCount++;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800199 }
200
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800201 entryRemoved(true, key, value, null);
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800202 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800203 }
204
205 /**
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800206 * Removes the entry for {@code key} if it exists.
207 *
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800208 * @return the previous value mapped by {@code key}.
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800209 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800210 public final V remove(K key) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800211 if (key == null) {
212 throw new NullPointerException("key == null");
213 }
214
Jesse Wilson7db1b402011-02-25 16:38:40 -0800215 V previous;
216 synchronized (this) {
217 previous = map.remove(key);
218 if (previous != null) {
219 size -= safeSizeOf(key, previous);
220 }
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800221 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800222
223 if (previous != null) {
224 entryRemoved(false, key, previous, null);
225 }
226
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800227 return previous;
228 }
229
230 /**
Jesse Wilson7db1b402011-02-25 16:38:40 -0800231 * Called for entries that have been evicted or removed. This method is
232 * invoked when a value is evicted to make space, removed by a call to
233 * {@link #remove}, or replaced by a call to {@link #put}. The default
234 * implementation does nothing.
235 *
236 * <p>The method is called without synchronization: other threads may
237 * access the cache while this method is executing.
238 *
239 * @param evicted true if the entry is being removed to make space, false
240 * if the removal was caused by a {@link #put} or {@link #remove}.
241 * @param newValue the new value for {@code key}, if it exists. If non-null,
242 * this removal was caused by a {@link #put}. Otherwise it was caused by
243 * an eviction or a {@link #remove}.
244 */
245 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800246
247 /**
248 * Called after a cache miss to compute a value for the corresponding key.
249 * Returns the computed value or null if no value can be computed. The
250 * default implementation returns null.
Jesse Wilson7db1b402011-02-25 16:38:40 -0800251 *
252 * <p>The method is called without synchronization: other threads may
253 * access the cache while this method is executing.
254 *
255 * <p>If a value for {@code key} exists in the cache when this method
256 * returns, the created value will be released with {@link #entryRemoved}
257 * and discarded. This can occur when multiple threads request the same key
258 * at the same time (causing multiple values to be created), or when one
259 * thread calls {@link #put} while another is creating a value for the same
260 * key.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800261 */
262 protected V create(K key) {
263 return null;
264 }
265
266 private int safeSizeOf(K key, V value) {
267 int result = sizeOf(key, value);
268 if (result < 0) {
269 throw new IllegalStateException("Negative size: " + key + "=" + value);
270 }
271 return result;
272 }
273
274 /**
275 * Returns the size of the entry for {@code key} and {@code value} in
276 * user-defined units. The default implementation returns 1 so that size
277 * is the number of entries and max size is the maximum number of entries.
278 *
279 * <p>An entry's size must not change while it is in the cache.
280 */
281 protected int sizeOf(K key, V value) {
282 return 1;
283 }
284
285 /**
Jesse Wilsonc4e62092011-02-25 17:57:04 -0800286 * Clear the cache, calling {@link #entryRemoved} on each removed entry.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800287 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800288 public final void evictAll() {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800289 trimToSize(-1); // -1 will evict 0-sized elements
290 }
291
292 /**
Jesse Wilson9b5a9352011-02-10 11:19:09 -0800293 * For caches that do not override {@link #sizeOf}, this returns the number
294 * of entries in the cache. For all other caches, this returns the sum of
295 * the sizes of the entries in this cache.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800296 */
297 public synchronized final int size() {
298 return size;
299 }
300
301 /**
Jesse Wilson9b5a9352011-02-10 11:19:09 -0800302 * For caches that do not override {@link #sizeOf}, this returns the maximum
303 * number of entries in the cache. For all other caches, this returns the
304 * maximum sum of the sizes of the entries in this cache.
305 */
306 public synchronized final int maxSize() {
307 return maxSize;
308 }
309
310 /**
Jesse Wilsona460c182011-03-16 14:57:01 -0700311 * Returns the number of times {@link #get} returned a value that was
312 * already present in the cache.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800313 */
314 public synchronized final int hitCount() {
315 return hitCount;
316 }
317
318 /**
319 * Returns the number of times {@link #get} returned null or required a new
320 * value to be created.
321 */
322 public synchronized final int missCount() {
323 return missCount;
324 }
325
326 /**
327 * Returns the number of times {@link #create(Object)} returned a value.
328 */
329 public synchronized final int createCount() {
330 return createCount;
331 }
332
333 /**
334 * Returns the number of times {@link #put} was called.
335 */
336 public synchronized final int putCount() {
337 return putCount;
338 }
339
340 /**
341 * Returns the number of values that have been evicted.
342 */
343 public synchronized final int evictionCount() {
344 return evictionCount;
345 }
346
347 /**
348 * Returns a copy of the current contents of the cache, ordered from least
349 * recently accessed to most recently accessed.
350 */
351 public synchronized final Map<K, V> snapshot() {
352 return new LinkedHashMap<K, V>(map);
353 }
354
355 @Override public synchronized final String toString() {
356 int accesses = hitCount + missCount;
357 int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
358 return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
359 maxSize, hitCount, missCount, hitPercent);
360 }
361}