blob: 834dac339543e94a12734d3622f760930a2db33a [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 Wilsone2c1f4a2011-02-07 14:26:26 -080057 */
58public class LruCache<K, V> {
59 private final LinkedHashMap<K, V> map;
60
61 /** Size of this cache in units. Not necessarily the number of elements. */
62 private int size;
Jesse Wilson9b5a9352011-02-10 11:19:09 -080063 private int maxSize;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080064
65 private int putCount;
66 private int createCount;
67 private int evictionCount;
68 private int hitCount;
69 private int missCount;
70
71 /**
72 * @param maxSize for caches that do not override {@link #sizeOf}, this is
73 * the maximum number of entries in the cache. For all other caches,
74 * this is the maximum sum of the sizes of the entries in this cache.
75 */
76 public LruCache(int maxSize) {
77 if (maxSize <= 0) {
78 throw new IllegalArgumentException("maxSize <= 0");
79 }
80 this.maxSize = maxSize;
81 this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
82 }
83
84 /**
85 * Returns the value for {@code key} if it exists in the cache or can be
86 * created by {@code #create}. If a value was returned, it is moved to the
87 * head of the queue. This returns null if a value is not cached and cannot
88 * be created.
89 */
Jesse Wilson7db1b402011-02-25 16:38:40 -080090 public final V get(K key) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080091 if (key == null) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -080092 throw new NullPointerException("key == null");
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -080093 }
94
Jesse Wilson7db1b402011-02-25 16:38:40 -080095 V mapValue;
96 synchronized (this) {
97 mapValue = map.get(key);
98 if (mapValue != null) {
99 hitCount++;
100 return mapValue;
101 }
102 missCount++;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800103 }
104
Jesse Wilson7db1b402011-02-25 16:38:40 -0800105 /*
106 * Attempt to create a value. This may take a long time, and the map
107 * may be different when create() returns. If a conflicting value was
108 * added to the map while create() was working, we leave that value in
109 * the map and release the created value.
110 */
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800111
Jesse Wilson7db1b402011-02-25 16:38:40 -0800112 V createdValue = create(key);
113 if (createdValue == null) {
114 return null;
115 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800116
Jesse Wilson7db1b402011-02-25 16:38:40 -0800117 synchronized (this) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800118 createCount++;
Jesse Wilson7db1b402011-02-25 16:38:40 -0800119 mapValue = map.put(key, createdValue);
120
121 if (mapValue != null) {
122 // There was a conflict so undo that last put
123 map.put(key, mapValue);
124 } else {
125 size += safeSizeOf(key, createdValue);
126 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800127 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800128
129 if (mapValue != null) {
130 entryRemoved(false, key, createdValue, mapValue);
131 return mapValue;
132 } else {
133 trimToSize(maxSize);
134 return createdValue;
135 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800136 }
137
138 /**
139 * Caches {@code value} for {@code key}. The value is moved to the head of
140 * the queue.
141 *
Jesse Wilsondfed7c02011-02-25 17:57:04 -0800142 * @return the previous value mapped by {@code key}.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800143 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800144 public final V put(K key, V value) {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800145 if (key == null || value == null) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800146 throw new NullPointerException("key == null || value == null");
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800147 }
148
Jesse Wilson7db1b402011-02-25 16:38:40 -0800149 V previous;
150 synchronized (this) {
151 putCount++;
152 size += safeSizeOf(key, value);
153 previous = map.put(key, value);
154 if (previous != null) {
155 size -= safeSizeOf(key, previous);
156 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800157 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800158
159 if (previous != null) {
160 entryRemoved(false, key, previous, value);
161 }
162
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800163 trimToSize(maxSize);
164 return previous;
165 }
166
Jesse Wilson7db1b402011-02-25 16:38:40 -0800167 /**
168 * @param maxSize the maximum size of the cache before returning. May be -1
169 * to evict even 0-sized elements.
170 */
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800171 private void trimToSize(int maxSize) {
Jesse Wilson7db1b402011-02-25 16:38:40 -0800172 while (true) {
173 K key;
174 V value;
175 synchronized (this) {
176 if (size < 0 || (map.isEmpty() && size != 0)) {
177 throw new IllegalStateException(getClass().getName()
178 + ".sizeOf() is reporting inconsistent results!");
179 }
180
181 if (size <= maxSize) {
182 break;
183 }
184
185 Map.Entry<K, V> toEvict = map.eldest();
186 if (toEvict == null) {
187 break;
188 }
189
190 key = toEvict.getKey();
191 value = toEvict.getValue();
192 map.remove(key);
193 size -= safeSizeOf(key, value);
194 evictionCount++;
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800195 }
196
Jesse Wilsondfed7c02011-02-25 17:57:04 -0800197 entryRemoved(true, key, value, null);
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800198 }
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800199 }
200
201 /**
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800202 * Removes the entry for {@code key} if it exists.
203 *
Jesse Wilsondfed7c02011-02-25 17:57:04 -0800204 * @return the previous value mapped by {@code key}.
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800205 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800206 public final V remove(K key) {
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800207 if (key == null) {
208 throw new NullPointerException("key == null");
209 }
210
Jesse Wilson7db1b402011-02-25 16:38:40 -0800211 V previous;
212 synchronized (this) {
213 previous = map.remove(key);
214 if (previous != null) {
215 size -= safeSizeOf(key, previous);
216 }
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800217 }
Jesse Wilson7db1b402011-02-25 16:38:40 -0800218
219 if (previous != null) {
220 entryRemoved(false, key, previous, null);
221 }
222
Jesse Wilson56b6ad32011-02-11 13:32:04 -0800223 return previous;
224 }
225
226 /**
Jesse Wilson7db1b402011-02-25 16:38:40 -0800227 * Called for entries that have been evicted or removed. This method is
228 * invoked when a value is evicted to make space, removed by a call to
229 * {@link #remove}, or replaced by a call to {@link #put}. The default
230 * implementation does nothing.
231 *
232 * <p>The method is called without synchronization: other threads may
233 * access the cache while this method is executing.
234 *
235 * @param evicted true if the entry is being removed to make space, false
236 * if the removal was caused by a {@link #put} or {@link #remove}.
237 * @param newValue the new value for {@code key}, if it exists. If non-null,
238 * this removal was caused by a {@link #put}. Otherwise it was caused by
239 * an eviction or a {@link #remove}.
240 */
241 protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800242
243 /**
244 * Called after a cache miss to compute a value for the corresponding key.
245 * Returns the computed value or null if no value can be computed. The
246 * default implementation returns null.
Jesse Wilson7db1b402011-02-25 16:38:40 -0800247 *
248 * <p>The method is called without synchronization: other threads may
249 * access the cache while this method is executing.
250 *
251 * <p>If a value for {@code key} exists in the cache when this method
252 * returns, the created value will be released with {@link #entryRemoved}
253 * and discarded. This can occur when multiple threads request the same key
254 * at the same time (causing multiple values to be created), or when one
255 * thread calls {@link #put} while another is creating a value for the same
256 * key.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800257 */
258 protected V create(K key) {
259 return null;
260 }
261
262 private int safeSizeOf(K key, V value) {
263 int result = sizeOf(key, value);
264 if (result < 0) {
265 throw new IllegalStateException("Negative size: " + key + "=" + value);
266 }
267 return result;
268 }
269
270 /**
271 * Returns the size of the entry for {@code key} and {@code value} in
272 * user-defined units. The default implementation returns 1 so that size
273 * is the number of entries and max size is the maximum number of entries.
274 *
275 * <p>An entry's size must not change while it is in the cache.
276 */
277 protected int sizeOf(K key, V value) {
278 return 1;
279 }
280
281 /**
Jesse Wilsondfed7c02011-02-25 17:57:04 -0800282 * Clear the cache, calling {@link #entryRemoved} on each removed entry.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800283 */
Jesse Wilson7db1b402011-02-25 16:38:40 -0800284 public final void evictAll() {
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800285 trimToSize(-1); // -1 will evict 0-sized elements
286 }
287
288 /**
Jesse Wilson9b5a9352011-02-10 11:19:09 -0800289 * For caches that do not override {@link #sizeOf}, this returns the number
290 * of entries in the cache. For all other caches, this returns the sum of
291 * the sizes of the entries in this cache.
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800292 */
293 public synchronized final int size() {
294 return size;
295 }
296
297 /**
Jesse Wilson9b5a9352011-02-10 11:19:09 -0800298 * For caches that do not override {@link #sizeOf}, this returns the maximum
299 * number of entries in the cache. For all other caches, this returns the
300 * maximum sum of the sizes of the entries in this cache.
301 */
302 public synchronized final int maxSize() {
303 return maxSize;
304 }
305
306 /**
Jesse Wilsone2c1f4a2011-02-07 14:26:26 -0800307 * Returns the number of times {@link #get} returned a value.
308 */
309 public synchronized final int hitCount() {
310 return hitCount;
311 }
312
313 /**
314 * Returns the number of times {@link #get} returned null or required a new
315 * value to be created.
316 */
317 public synchronized final int missCount() {
318 return missCount;
319 }
320
321 /**
322 * Returns the number of times {@link #create(Object)} returned a value.
323 */
324 public synchronized final int createCount() {
325 return createCount;
326 }
327
328 /**
329 * Returns the number of times {@link #put} was called.
330 */
331 public synchronized final int putCount() {
332 return putCount;
333 }
334
335 /**
336 * Returns the number of values that have been evicted.
337 */
338 public synchronized final int evictionCount() {
339 return evictionCount;
340 }
341
342 /**
343 * Returns a copy of the current contents of the cache, ordered from least
344 * recently accessed to most recently accessed.
345 */
346 public synchronized final Map<K, V> snapshot() {
347 return new LinkedHashMap<K, V>(map);
348 }
349
350 @Override public synchronized final String toString() {
351 int accesses = hitCount + missCount;
352 int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
353 return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
354 maxSize, hitCount, missCount, hitPercent);
355 }
356}