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