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.util;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 
21 import java.util.LinkedHashMap;
22 import 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,
31  * override {@link #entryRemoved}.
32  *
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
38  * {@link #sizeOf} to size the cache in different units. For example, this cache
39  * is limited to 4MiB of bitmaps:
40  * <pre>   {@code
41  *   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);
53  *     }
54  *   }}</pre>
55  *
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.
59  *
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.
63  */
64 public class LruCache<K, V> {
65     @UnsupportedAppUsage
66     private final LinkedHashMap<K, V> map;
67 
68     /** Size of this cache in units. Not necessarily the number of elements. */
69     private int size;
70     private int maxSize;
71 
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      */
LruCache(int maxSize)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     /**
92      * Sets the size of the cache.
93      *
94      * @param maxSize The new maximum size.
95      */
resize(int maxSize)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     /**
108      * 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      */
get(K key)113     public final V get(K key) {
114         if (key == null) {
115             throw new NullPointerException("key == null");
116         }
117 
118         V mapValue;
119         synchronized (this) {
120             mapValue = map.get(key);
121             if (mapValue != null) {
122                 hitCount++;
123                 return mapValue;
124             }
125             missCount++;
126         }
127 
128         /*
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          */
134 
135         V createdValue = create(key);
136         if (createdValue == null) {
137             return null;
138         }
139 
140         synchronized (this) {
141             createCount++;
142             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             }
150         }
151 
152         if (mapValue != null) {
153             entryRemoved(false, key, createdValue, mapValue);
154             return mapValue;
155         } else {
156             trimToSize(maxSize);
157             return createdValue;
158         }
159     }
160 
161     /**
162      * Caches {@code value} for {@code key}. The value is moved to the head of
163      * the queue.
164      *
165      * @return the previous value mapped by {@code key}.
166      */
put(K key, V value)167     public final V put(K key, V value) {
168         if (key == null || value == null) {
169             throw new NullPointerException("key == null || value == null");
170         }
171 
172         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             }
180         }
181 
182         if (previous != null) {
183             entryRemoved(false, key, previous, value);
184         }
185 
186         trimToSize(maxSize);
187         return previous;
188     }
189 
190     /**
191      * Remove the eldest entries until the total of remaining entries is at or
192      * below the requested size.
193      *
194      * @param maxSize the maximum size of the cache before returning. May be -1
195      *            to evict even 0-sized elements.
196      */
trimToSize(int maxSize)197     public void trimToSize(int maxSize) {
198         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++;
221             }
222 
223             entryRemoved(true, key, value, null);
224         }
225     }
226 
227     /**
228      * Removes the entry for {@code key} if it exists.
229      *
230      * @return the previous value mapped by {@code key}.
231      */
remove(K key)232     public final V remove(K key) {
233         if (key == null) {
234             throw new NullPointerException("key == null");
235         }
236 
237         V previous;
238         synchronized (this) {
239             previous = map.remove(key);
240             if (previous != null) {
241                 size -= safeSizeOf(key, previous);
242             }
243         }
244 
245         if (previous != null) {
246             entryRemoved(false, key, previous, null);
247         }
248 
249         return previous;
250     }
251 
252     /**
253      * 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,
264      *     this removal was caused by a {@link #put} or a {@link #get}. Otherwise it was caused by
265      *     an eviction or a {@link #remove}.
266      */
entryRemoved(boolean evicted, K key, V oldValue, V newValue)267     protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
268 
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.
273      *
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.
283      */
create(K key)284     protected V create(K key) {
285         return null;
286     }
287 
safeSizeOf(K key, V value)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      */
sizeOf(K key, V value)303     protected int sizeOf(K key, V value) {
304         return 1;
305     }
306 
307     /**
308      * Clear the cache, calling {@link #entryRemoved} on each removed entry.
309      */
evictAll()310     public final void evictAll() {
311         trimToSize(-1); // -1 will evict 0-sized elements
312     }
313 
314     /**
315      * 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.
318      */
size()319     public synchronized final int size() {
320         return size;
321     }
322 
323     /**
324      * 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      */
maxSize()328     public synchronized final int maxSize() {
329         return maxSize;
330     }
331 
332     /**
333      * Returns the number of times {@link #get} returned a value that was
334      * already present in the cache.
335      */
hitCount()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      */
missCount()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      */
createCount()351     public synchronized final int createCount() {
352         return createCount;
353     }
354 
355     /**
356      * Returns the number of times {@link #put} was called.
357      */
putCount()358     public synchronized final int putCount() {
359         return putCount;
360     }
361 
362     /**
363      * Returns the number of values that have been evicted.
364      */
evictionCount()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      */
snapshot()373     public synchronized final Map<K, V> snapshot() {
374         return new LinkedHashMap<K, V>(map);
375     }
376 
toString()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 }
384