• 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 java.util.LinkedHashMap;
20 import 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,
29  * override {@link #entryRemoved}.
30  *
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
36  * {@link #sizeOf} to size the cache in different units. For example, this cache
37  * is limited to 4MiB of bitmaps:
38  * <pre>   {@code
39  *   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);
51  *     }
52  *   }}</pre>
53  *
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.
57  *
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.
61  */
62 public 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;
67     private int maxSize;
68 
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      */
LruCache(int maxSize)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      * Sets the size of the cache.
90      *
91      * @param maxSize The new maximum size.
92      */
resize(int maxSize)93     public void resize(int maxSize) {
94         if (maxSize <= 0) {
95             throw new IllegalArgumentException("maxSize <= 0");
96         }
97 
98         synchronized (this) {
99             this.maxSize = maxSize;
100         }
101         trimToSize(maxSize);
102     }
103 
104     /**
105      * Returns the value for {@code key} if it exists in the cache or can be
106      * created by {@code #create}. If a value was returned, it is moved to the
107      * head of the queue. This returns null if a value is not cached and cannot
108      * be created.
109      */
get(K key)110     public final V get(K key) {
111         if (key == null) {
112             throw new NullPointerException("key == null");
113         }
114 
115         V mapValue;
116         synchronized (this) {
117             mapValue = map.get(key);
118             if (mapValue != null) {
119                 hitCount++;
120                 return mapValue;
121             }
122             missCount++;
123         }
124 
125         /*
126          * Attempt to create a value. This may take a long time, and the map
127          * may be different when create() returns. If a conflicting value was
128          * added to the map while create() was working, we leave that value in
129          * the map and release the created value.
130          */
131 
132         V createdValue = create(key);
133         if (createdValue == null) {
134             return null;
135         }
136 
137         synchronized (this) {
138             createCount++;
139             mapValue = map.put(key, createdValue);
140 
141             if (mapValue != null) {
142                 // There was a conflict so undo that last put
143                 map.put(key, mapValue);
144             } else {
145                 size += safeSizeOf(key, createdValue);
146             }
147         }
148 
149         if (mapValue != null) {
150             entryRemoved(false, key, createdValue, mapValue);
151             return mapValue;
152         } else {
153             trimToSize(maxSize);
154             return createdValue;
155         }
156     }
157 
158     /**
159      * Caches {@code value} for {@code key}. The value is moved to the head of
160      * the queue.
161      *
162      * @return the previous value mapped by {@code key}.
163      */
put(K key, V value)164     public final V put(K key, V value) {
165         if (key == null || value == null) {
166             throw new NullPointerException("key == null || value == null");
167         }
168 
169         V previous;
170         synchronized (this) {
171             putCount++;
172             size += safeSizeOf(key, value);
173             previous = map.put(key, value);
174             if (previous != null) {
175                 size -= safeSizeOf(key, previous);
176             }
177         }
178 
179         if (previous != null) {
180             entryRemoved(false, key, previous, value);
181         }
182 
183         trimToSize(maxSize);
184         return previous;
185     }
186 
187     /**
188      * Remove the eldest entries until the total of remaining entries is at or
189      * below the requested size.
190      *
191      * @param maxSize the maximum size of the cache before returning. May be -1
192      *            to evict even 0-sized elements.
193      */
trimToSize(int maxSize)194     public void trimToSize(int maxSize) {
195         while (true) {
196             K key;
197             V value;
198             synchronized (this) {
199                 if (size < 0 || (map.isEmpty() && size != 0)) {
200                     throw new IllegalStateException(getClass().getName()
201                             + ".sizeOf() is reporting inconsistent results!");
202                 }
203 
204                 if (size <= maxSize) {
205                     break;
206                 }
207 
208                 Map.Entry<K, V> toEvict = map.eldest();
209                 if (toEvict == null) {
210                     break;
211                 }
212 
213                 key = toEvict.getKey();
214                 value = toEvict.getValue();
215                 map.remove(key);
216                 size -= safeSizeOf(key, value);
217                 evictionCount++;
218             }
219 
220             entryRemoved(true, key, value, null);
221         }
222     }
223 
224     /**
225      * Removes the entry for {@code key} if it exists.
226      *
227      * @return the previous value mapped by {@code key}.
228      */
remove(K key)229     public final V remove(K key) {
230         if (key == null) {
231             throw new NullPointerException("key == null");
232         }
233 
234         V previous;
235         synchronized (this) {
236             previous = map.remove(key);
237             if (previous != null) {
238                 size -= safeSizeOf(key, previous);
239             }
240         }
241 
242         if (previous != null) {
243             entryRemoved(false, key, previous, null);
244         }
245 
246         return previous;
247     }
248 
249     /**
250      * Called for entries that have been evicted or removed. This method is
251      * invoked when a value is evicted to make space, removed by a call to
252      * {@link #remove}, or replaced by a call to {@link #put}. The default
253      * implementation does nothing.
254      *
255      * <p>The method is called without synchronization: other threads may
256      * access the cache while this method is executing.
257      *
258      * @param evicted true if the entry is being removed to make space, false
259      *     if the removal was caused by a {@link #put} or {@link #remove}.
260      * @param newValue the new value for {@code key}, if it exists. If non-null,
261      *     this removal was caused by a {@link #put}. Otherwise it was caused by
262      *     an eviction or a {@link #remove}.
263      */
entryRemoved(boolean evicted, K key, V oldValue, V newValue)264     protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
265 
266     /**
267      * Called after a cache miss to compute a value for the corresponding key.
268      * Returns the computed value or null if no value can be computed. The
269      * default implementation returns null.
270      *
271      * <p>The method is called without synchronization: other threads may
272      * access the cache while this method is executing.
273      *
274      * <p>If a value for {@code key} exists in the cache when this method
275      * returns, the created value will be released with {@link #entryRemoved}
276      * and discarded. This can occur when multiple threads request the same key
277      * at the same time (causing multiple values to be created), or when one
278      * thread calls {@link #put} while another is creating a value for the same
279      * key.
280      */
create(K key)281     protected V create(K key) {
282         return null;
283     }
284 
safeSizeOf(K key, V value)285     private int safeSizeOf(K key, V value) {
286         int result = sizeOf(key, value);
287         if (result < 0) {
288             throw new IllegalStateException("Negative size: " + key + "=" + value);
289         }
290         return result;
291     }
292 
293     /**
294      * Returns the size of the entry for {@code key} and {@code value} in
295      * user-defined units.  The default implementation returns 1 so that size
296      * is the number of entries and max size is the maximum number of entries.
297      *
298      * <p>An entry's size must not change while it is in the cache.
299      */
sizeOf(K key, V value)300     protected int sizeOf(K key, V value) {
301         return 1;
302     }
303 
304     /**
305      * Clear the cache, calling {@link #entryRemoved} on each removed entry.
306      */
evictAll()307     public final void evictAll() {
308         trimToSize(-1); // -1 will evict 0-sized elements
309     }
310 
311     /**
312      * For caches that do not override {@link #sizeOf}, this returns the number
313      * of entries in the cache. For all other caches, this returns the sum of
314      * the sizes of the entries in this cache.
315      */
size()316     public synchronized final int size() {
317         return size;
318     }
319 
320     /**
321      * For caches that do not override {@link #sizeOf}, this returns the maximum
322      * number of entries in the cache. For all other caches, this returns the
323      * maximum sum of the sizes of the entries in this cache.
324      */
maxSize()325     public synchronized final int maxSize() {
326         return maxSize;
327     }
328 
329     /**
330      * Returns the number of times {@link #get} returned a value that was
331      * already present in the cache.
332      */
hitCount()333     public synchronized final int hitCount() {
334         return hitCount;
335     }
336 
337     /**
338      * Returns the number of times {@link #get} returned null or required a new
339      * value to be created.
340      */
missCount()341     public synchronized final int missCount() {
342         return missCount;
343     }
344 
345     /**
346      * Returns the number of times {@link #create(Object)} returned a value.
347      */
createCount()348     public synchronized final int createCount() {
349         return createCount;
350     }
351 
352     /**
353      * Returns the number of times {@link #put} was called.
354      */
putCount()355     public synchronized final int putCount() {
356         return putCount;
357     }
358 
359     /**
360      * Returns the number of values that have been evicted.
361      */
evictionCount()362     public synchronized final int evictionCount() {
363         return evictionCount;
364     }
365 
366     /**
367      * Returns a copy of the current contents of the cache, ordered from least
368      * recently accessed to most recently accessed.
369      */
snapshot()370     public synchronized final Map<K, V> snapshot() {
371         return new LinkedHashMap<K, V>(map);
372     }
373 
toString()374     @Override public synchronized final String toString() {
375         int accesses = hitCount + missCount;
376         int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
377         return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
378                 maxSize, hitCount, missCount, hitPercent);
379     }
380 }
381