LruCache.java revision 543146a82b87b33973743cbd1880e899ebba30f5
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
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,
29 * override {@link #entryEvicted}. This method is only invoked when values are
30 * evicted. Values replaced by calls to {@link #put} must be released manually.
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, this cache is
38 * 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 */
47public class LruCache<K, V> {
48    private final LinkedHashMap<K, V> map;
49
50    /** Size of this cache in units. Not necessarily the number of elements. */
51    private int size;
52    private final int maxSize;
53
54    private int putCount;
55    private int createCount;
56    private int evictionCount;
57    private int hitCount;
58    private int missCount;
59
60    /**
61     * @param maxSize for caches that do not override {@link #sizeOf}, this is
62     *     the maximum number of entries in the cache. For all other caches,
63     *     this is the maximum sum of the sizes of the entries in this cache.
64     */
65    public LruCache(int maxSize) {
66        if (maxSize <= 0) {
67            throw new IllegalArgumentException("maxSize <= 0");
68        }
69        this.maxSize = maxSize;
70        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
71    }
72
73    /**
74     * Returns the value for {@code key} if it exists in the cache or can be
75     * created by {@code #create}. If a value was returned, it is moved to the
76     * head of the queue. This returns null if a value is not cached and cannot
77     * be created.
78     */
79    public synchronized final V get(K key) {
80        if (key == null) {
81            throw new NullPointerException();
82        }
83
84        V result = map.get(key);
85        if (result != null) {
86            hitCount++;
87            return result;
88        }
89
90        missCount++;
91
92        // TODO: release the lock while calling this potentially slow user code
93        result = create(key);
94
95        if (result != null) {
96            createCount++;
97            size += safeSizeOf(key, result);
98            map.put(key, result);
99            trimToSize(maxSize);
100        }
101        return result;
102    }
103
104    /**
105     * Caches {@code value} for {@code key}. The value is moved to the head of
106     * the queue.
107     *
108     * @return the previous value mapped by {@code key}. Although that entry is
109     *     no longer cached, it has not been passed to {@link #entryEvicted}.
110     */
111    public synchronized final V put(K key, V value) {
112        if (key == null || value == null) {
113            throw new NullPointerException();
114        }
115
116        putCount++;
117        size += safeSizeOf(key, value);
118        V previous = map.put(key, value);
119        if (previous != null) {
120            size -= safeSizeOf(key, previous);
121        }
122        trimToSize(maxSize);
123        return previous;
124    }
125
126    private void trimToSize(int maxSize) {
127        while (size > maxSize) {
128            Map.Entry<K, V> toEvict = map.eldest();
129            if (toEvict == null) {
130                break; // map is empty; if size is not 0 then throw an error below
131            }
132
133            K key = toEvict.getKey();
134            V value = toEvict.getValue();
135            map.remove(key);
136            size -= safeSizeOf(key, value);
137            evictionCount++;
138
139            // TODO: release the lock while calling this potentially slow user code
140            entryEvicted(key, value);
141        }
142
143        if (size < 0 || (map.isEmpty() && size != 0)) {
144            throw new IllegalStateException(getClass().getName()
145                    + ".sizeOf() is reporting inconsistent results!");
146        }
147    }
148
149    /**
150     * Called for entries that have reached the tail of the least recently used
151     * queue and are be removed. The default implementation does nothing.
152     */
153    protected void entryEvicted(K key, V value) {}
154
155    /**
156     * Called after a cache miss to compute a value for the corresponding key.
157     * Returns the computed value or null if no value can be computed. The
158     * default implementation returns null.
159     */
160    protected V create(K key) {
161        return null;
162    }
163
164    private int safeSizeOf(K key, V value) {
165        int result = sizeOf(key, value);
166        if (result < 0) {
167            throw new IllegalStateException("Negative size: " + key + "=" + value);
168        }
169        return result;
170    }
171
172    /**
173     * Returns the size of the entry for {@code key} and {@code value} in
174     * user-defined units.  The default implementation returns 1 so that size
175     * is the number of entries and max size is the maximum number of entries.
176     *
177     * <p>An entry's size must not change while it is in the cache.
178     */
179    protected int sizeOf(K key, V value) {
180        return 1;
181    }
182
183    /**
184     * Clear the cache, calling {@link #entryEvicted} on each removed entry.
185     */
186    public synchronized final void evictAll() {
187        trimToSize(-1); // -1 will evict 0-sized elements
188    }
189
190    /**
191     * For caches that do not override {@link #sizeOf}, this is the number of
192     * entries in the cache. For all other caches, this is the sum of the sizes
193     * of the entries in this cache.
194     */
195    public synchronized final int size() {
196        return size;
197    }
198
199    /**
200     * Returns the number of times {@link #get} returned a value.
201     */
202    public synchronized final int hitCount() {
203        return hitCount;
204    }
205
206    /**
207     * Returns the number of times {@link #get} returned null or required a new
208     * value to be created.
209     */
210    public synchronized final int missCount() {
211        return missCount;
212    }
213
214    /**
215     * Returns the number of times {@link #create(Object)} returned a value.
216     */
217    public synchronized final int createCount() {
218        return createCount;
219    }
220
221    /**
222     * Returns the number of times {@link #put} was called.
223     */
224    public synchronized final int putCount() {
225        return putCount;
226    }
227
228    /**
229     * Returns the number of values that have been evicted.
230     */
231    public synchronized final int evictionCount() {
232        return evictionCount;
233    }
234
235    /**
236     * Returns a copy of the current contents of the cache, ordered from least
237     * recently accessed to most recently accessed.
238     */
239    public synchronized final Map<K, V> snapshot() {
240        return new LinkedHashMap<K, V>(map);
241    }
242
243    @Override public synchronized final String toString() {
244        int accesses = hitCount + missCount;
245        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
246        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
247                maxSize, hitCount, missCount, hitPercent);
248    }
249}
250