• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Iterator;
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24 
25 /**
26  * A cache that holds strong references to a limited number of values. Each time
27  * a value is accessed, it is moved to the head of a queue. When a value is
28  * added to a full cache, the value at the end of that queue is evicted and may
29  * become eligible for garbage collection.
30  *
31  * <p>If your cached values hold resources that need to be explicitly released,
32  * override {@link #entryRemoved}.
33  *
34  * <p>If a cache miss should be computed on demand for the corresponding keys,
35  * override {@link #create}. This simplifies the calling code, allowing it to
36  * assume a value will always be returned, even when there's a cache miss.
37  *
38  * <p>By default, the cache size is measured in the number of entries. Override
39  * {@link #sizeOf} to size the cache in different units. For example, this cache
40  * is limited to 4MiB of bitmaps:
41  * <pre>   {@code
42  *   int cacheSize = 4 * 1024 * 1024; // 4MiB
43  *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
44  *       protected int sizeOf(String key, Bitmap value) {
45  *           return value.getByteCount();
46  *       }
47  *   }}</pre>
48  *
49  * <p>This class is thread-safe. Perform multiple cache operations atomically by
50  * synchronizing on the cache: <pre>   {@code
51  *   synchronized (cache) {
52  *     if (cache.get(key) == null) {
53  *         cache.put(key, value);
54  *     }
55  *   }}</pre>
56  *
57  * <p>This class does not allow null to be used as a key or value. A return
58  * value of null from {@link #get}, {@link #put} or {@link #remove} is
59  * unambiguous: the key was not in the cache.
60  *
61  * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
62  * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
63  * Support Package</a> for earlier releases.
64  */
65 @android.ravenwood.annotation.RavenwoodKeepWholeClass
66 public class LruCache<K, V> {
67     @UnsupportedAppUsage
68     private final LinkedHashMap<K, V> map;
69 
70     /** Size of this cache in units. Not necessarily the number of elements. */
71     private int size;
72     private int maxSize;
73 
74     private int putCount;
75     private int createCount;
76     private int evictionCount;
77     private int hitCount;
78     private int missCount;
79 
80     /**
81      * @param maxSize for caches that do not override {@link #sizeOf}, this is
82      *     the maximum number of entries in the cache. For all other caches,
83      *     this is the maximum sum of the sizes of the entries in this cache.
84      */
LruCache(int maxSize)85     public LruCache(int maxSize) {
86         if (maxSize <= 0) {
87             throw new IllegalArgumentException("maxSize <= 0");
88         }
89         this.maxSize = maxSize;
90         this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
91     }
92 
93     /**
94      * Sets the size of the cache.
95      *
96      * @param maxSize The new maximum size.
97      */
resize(int maxSize)98     public void resize(int maxSize) {
99         if (maxSize <= 0) {
100             throw new IllegalArgumentException("maxSize <= 0");
101         }
102 
103         synchronized (this) {
104             this.maxSize = maxSize;
105         }
106         trimToSize(maxSize);
107     }
108 
109     /**
110      * Returns the value for {@code key} if it exists in the cache or can be
111      * created by {@code #create}. If a value was returned, it is moved to the
112      * head of the queue. This returns null if a value is not cached and cannot
113      * be created.
114      */
get(K key)115     public final V get(K key) {
116         if (key == null) {
117             throw new NullPointerException("key == null");
118         }
119 
120         V mapValue;
121         synchronized (this) {
122             mapValue = map.get(key);
123             if (mapValue != null) {
124                 hitCount++;
125                 return mapValue;
126             }
127             missCount++;
128         }
129 
130         /*
131          * Attempt to create a value. This may take a long time, and the map
132          * may be different when create() returns. If a conflicting value was
133          * added to the map while create() was working, we leave that value in
134          * the map and release the created value.
135          */
136 
137         V createdValue = create(key);
138         if (createdValue == null) {
139             return null;
140         }
141 
142         synchronized (this) {
143             createCount++;
144             mapValue = map.put(key, createdValue);
145 
146             if (mapValue != null) {
147                 // There was a conflict so undo that last put
148                 map.put(key, mapValue);
149             } else {
150                 size += safeSizeOf(key, createdValue);
151             }
152         }
153 
154         if (mapValue != null) {
155             entryRemoved(false, key, createdValue, mapValue);
156             return mapValue;
157         } else {
158             trimToSize(maxSize);
159             return createdValue;
160         }
161     }
162 
163     /**
164      * Caches {@code value} for {@code key}. The value is moved to the head of
165      * the queue.
166      *
167      * @return the previous value mapped by {@code key}.
168      */
put(K key, V value)169     public final V put(K key, V value) {
170         if (key == null || value == null) {
171             throw new NullPointerException("key == null || value == null");
172         }
173 
174         V previous;
175         synchronized (this) {
176             putCount++;
177             size += safeSizeOf(key, value);
178             previous = map.put(key, value);
179             if (previous != null) {
180                 size -= safeSizeOf(key, previous);
181             }
182         }
183 
184         if (previous != null) {
185             entryRemoved(false, key, previous, value);
186         }
187 
188         trimToSize(maxSize);
189         return previous;
190     }
191 
192     /**
193      * Remove the eldest entries until the total of remaining entries is at or
194      * below the requested size.
195      *
196      * @param maxSize the maximum size of the cache before returning. May be -1
197      *            to evict even 0-sized elements.
198      */
trimToSize(int maxSize)199     public void trimToSize(int maxSize) {
200         while (true) {
201             K key;
202             V value;
203             synchronized (this) {
204                 if (size < 0 || (map.isEmpty() && size != 0)) {
205                     throw new IllegalStateException(getClass().getName()
206                             + ".sizeOf() is reporting inconsistent results!");
207                 }
208 
209                 if (size <= maxSize) {
210                     break;
211                 }
212 
213                 Map.Entry<K, V> toEvict = eldest();
214                 if (toEvict == null) {
215                     break;
216                 }
217 
218                 key = toEvict.getKey();
219                 value = toEvict.getValue();
220                 map.remove(key);
221                 size -= safeSizeOf(key, value);
222                 evictionCount++;
223             }
224 
225             entryRemoved(true, key, value, null);
226         }
227     }
228 
229     @android.ravenwood.annotation.RavenwoodReplace
eldest()230     private Map.Entry<K, V> eldest() {
231         return map.eldest();
232     }
233 
eldest$ravenwood()234     private Map.Entry<K, V> eldest$ravenwood() {
235         final Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();
236         return it.hasNext() ? it.next() : null;
237     }
238 
239     /**
240      * Removes the entry for {@code key} if it exists.
241      *
242      * @return the previous value mapped by {@code key}.
243      */
remove(K key)244     public final V remove(K key) {
245         if (key == null) {
246             throw new NullPointerException("key == null");
247         }
248 
249         V previous;
250         synchronized (this) {
251             previous = map.remove(key);
252             if (previous != null) {
253                 size -= safeSizeOf(key, previous);
254             }
255         }
256 
257         if (previous != null) {
258             entryRemoved(false, key, previous, null);
259         }
260 
261         return previous;
262     }
263 
264     /**
265      * Called for entries that have been evicted or removed. This method is
266      * invoked when a value is evicted to make space, removed by a call to
267      * {@link #remove}, or replaced by a call to {@link #put}. The default
268      * implementation does nothing.
269      *
270      * <p>The method is called without synchronization: other threads may
271      * access the cache while this method is executing.
272      *
273      * @param evicted true if the entry is being removed to make space, false
274      *     if the removal was caused by a {@link #put} or {@link #remove}.
275      * @param newValue the new value for {@code key}, if it exists. If non-null,
276      *     this removal was caused by a {@link #put} or a {@link #get}. Otherwise it was caused by
277      *     an eviction or a {@link #remove}.
278      */
entryRemoved(boolean evicted, K key, V oldValue, V newValue)279     protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
280 
281     /**
282      * Called after a cache miss to compute a value for the corresponding key.
283      * Returns the computed value or null if no value can be computed. The
284      * default implementation returns null.
285      *
286      * <p>The method is called without synchronization: other threads may
287      * access the cache while this method is executing.
288      *
289      * <p>If a value for {@code key} exists in the cache when this method
290      * returns, the created value will be released with {@link #entryRemoved}
291      * and discarded. This can occur when multiple threads request the same key
292      * at the same time (causing multiple values to be created), or when one
293      * thread calls {@link #put} while another is creating a value for the same
294      * key.
295      */
create(K key)296     protected V create(K key) {
297         return null;
298     }
299 
safeSizeOf(K key, V value)300     private int safeSizeOf(K key, V value) {
301         int result = sizeOf(key, value);
302         if (result < 0) {
303             throw new IllegalStateException("Negative size: " + key + "=" + value);
304         }
305         return result;
306     }
307 
308     /**
309      * Returns the size of the entry for {@code key} and {@code value} in
310      * user-defined units.  The default implementation returns 1 so that size
311      * is the number of entries and max size is the maximum number of entries.
312      *
313      * <p>An entry's size must not change while it is in the cache.
314      */
sizeOf(K key, V value)315     protected int sizeOf(K key, V value) {
316         return 1;
317     }
318 
319     /**
320      * Clear the cache, calling {@link #entryRemoved} on each removed entry.
321      */
evictAll()322     public final void evictAll() {
323         trimToSize(-1); // -1 will evict 0-sized elements
324     }
325 
326     /**
327      * For caches that do not override {@link #sizeOf}, this returns the number
328      * of entries in the cache. For all other caches, this returns the sum of
329      * the sizes of the entries in this cache.
330      */
size()331     public synchronized final int size() {
332         return size;
333     }
334 
335     /**
336      * For caches that do not override {@link #sizeOf}, this returns the maximum
337      * number of entries in the cache. For all other caches, this returns the
338      * maximum sum of the sizes of the entries in this cache.
339      */
maxSize()340     public synchronized final int maxSize() {
341         return maxSize;
342     }
343 
344     /**
345      * Returns the number of times {@link #get} returned a value that was
346      * already present in the cache.
347      */
hitCount()348     public synchronized final int hitCount() {
349         return hitCount;
350     }
351 
352     /**
353      * Returns the number of times {@link #get} returned null or required a new
354      * value to be created.
355      */
missCount()356     public synchronized final int missCount() {
357         return missCount;
358     }
359 
360     /**
361      * Returns the number of times {@link #create(Object)} returned a value.
362      */
createCount()363     public synchronized final int createCount() {
364         return createCount;
365     }
366 
367     /**
368      * Returns the number of times {@link #put} was called.
369      */
putCount()370     public synchronized final int putCount() {
371         return putCount;
372     }
373 
374     /**
375      * Returns the number of values that have been evicted.
376      */
evictionCount()377     public synchronized final int evictionCount() {
378         return evictionCount;
379     }
380 
381     /**
382      * Returns a copy of the current contents of the cache, ordered from least
383      * recently accessed to most recently accessed.
384      */
snapshot()385     public synchronized final Map<K, V> snapshot() {
386         return new LinkedHashMap<K, V>(map);
387     }
388 
toString()389     @Override public synchronized final String toString() {
390         int accesses = hitCount + missCount;
391         int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
392         return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
393                 maxSize, hitCount, missCount, hitPercent);
394     }
395 }
396