2 * Copyright (C) 2011 The Android Open Source Project Licensed under the Apache
3 * License, Version 2.0 (the "License"); you may not use this file except in
4 * compliance with the License. You may obtain a copy of the License at
5 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6 * or agreed to in writing, software distributed under the License is
7 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8 * KIND, either express or implied. See the License for the specific language
9 * governing permissions and limitations under the License.
12 package com.cyanogenmod.eleven.cache;
14 // NOTE: upstream of this class is android.util.LruCache, changes below
15 // expose trimToSize() to be called externally.
17 import android.annotation.SuppressLint;
19 import java.util.LinkedHashMap;
23 * Static library version of {@link android.util.LruCache}. Used to write apps
24 * that run on API levels prior to 12. When running on API level 12 or above,
25 * this implementation is still used; it does not try to switch to the
26 * framework's implementation. See the framework SDK documentation for a class
29 public class LruCache<K, V> {
31 private final LinkedHashMap<K, V> map;
33 private final int maxSize;
35 /** Size of this cache in units. Not necessarily the number of elements. */
40 private int createCount;
42 private int evictionCount;
46 private int missCount;
49 * @param maxSize for caches that do not override {@link #sizeOf}, this is
50 * the maximum number of entries in the cache. For all other
51 * caches, this is the maximum sum of the sizes of the entries in
54 public LruCache(final int maxSize) {
56 throw new IllegalArgumentException("maxSize <= 0");
58 this.maxSize = maxSize;
59 this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
63 * Returns the value for {@code key} if it exists in the cache or can be
64 * created by {@code #create}. If a value was returned, it is moved to the
65 * head of the queue. This returns null if a value is not cached and cannot
68 public final V get(final K key) {
70 throw new NullPointerException("key == null");
75 mapValue = map.get(key);
76 if (mapValue != null) {
84 * Attempt to create a value. This may take a long time, and the map may
85 * be different when create() returns. If a conflicting value was added
86 * to the map while create() was working, we leave that value in the map
87 * and release the created value.
90 final V createdValue = create(key);
91 if (createdValue == null) {
97 mapValue = map.put(key, createdValue);
99 if (mapValue != null) {
100 /* There was a conflict so undo that last put */
101 this.map.put(key, mapValue);
103 this.size += safeSizeOf(key, createdValue);
107 if (mapValue != null) {
108 entryRemoved(false, key, createdValue, mapValue);
117 * Caches {@code value} for {@code key}. The value is moved to the head of
120 * @return the previous value mapped by {@code key}.
122 public final V put(final K key, final V value) {
123 if (key == null || value == null) {
124 throw new NullPointerException("key == null || value == null");
128 synchronized (this) {
130 this.size += safeSizeOf(key, value);
131 previous = this.map.put(key, value);
132 if (previous != null) {
133 this.size -= safeSizeOf(key, previous);
137 if (previous != null) {
138 entryRemoved(false, key, previous, value);
146 * @param maxSize the maximum size of the cache before returning. May be -1
147 * to evict even 0-sized elements.
149 public void trimToSize(final int maxSize) {
153 synchronized (this) {
154 if (this.size < 0 || this.map.isEmpty() && size != 0) {
155 throw new IllegalStateException(getClass().getName()
156 + ".sizeOf() is reporting inconsistent results!");
159 if (this.size <= maxSize || this.map.isEmpty()) {
163 final Map.Entry<K, V> toEvict = this.map.entrySet().iterator().next();
164 key = toEvict.getKey();
165 value = toEvict.getValue();
166 this.map.remove(key);
167 this.size -= safeSizeOf(key, value);
168 this.evictionCount++;
171 entryRemoved(true, key, value, null);
176 * Removes the entry for {@code key} if it exists.
178 * @return the previous value mapped by {@code key}.
180 public final V remove(final K key) {
182 throw new NullPointerException("key == null");
186 synchronized (this) {
187 previous = this.map.remove(key);
188 if (previous != null) {
189 this.size -= safeSizeOf(key, previous);
193 if (previous != null) {
194 entryRemoved(false, key, previous, null);
201 * Called for entries that have been evicted or removed. This method is
202 * invoked when a value is evicted to make space, removed by a call to
203 * {@link #remove}, or replaced by a call to {@link #put}. The default
204 * implementation does nothing.
206 * The method is called without synchronization: other threads may access
207 * the cache while this method is executing.
209 * @param evicted true if the entry is being removed to make space, false if
210 * the removal was caused by a {@link #put} or {@link #remove}.
211 * @param newValue the new value for {@code key}, if it exists. If non-null,
212 * this removal was caused by a {@link #put}. Otherwise it was
213 * caused by an eviction or a {@link #remove}.
215 protected void entryRemoved(final boolean evicted, final K key, final V oldValue,
220 * Called after a cache miss to compute a value for the corresponding key.
221 * Returns the computed value or null if no value can be computed. The
222 * default implementation returns null.
224 * The method is called without synchronization: other threads may access
225 * the cache while this method is executing.
227 * If a value for {@code key} exists in the cache when this method returns,
228 * the created value will be released with {@link #entryRemoved} and
229 * discarded. This can occur when multiple threads request the same key at
230 * the same time (causing multiple values to be created), or when one thread
231 * calls {@link #put} while another is creating a value for the same key.
233 protected V create(final K key) {
237 private int safeSizeOf(final K key, final V value) {
238 final int result = sizeOf(key, value);
240 throw new IllegalStateException("Negative size: " + key + "=" + value);
246 * Returns the size of the entry for {@code key} and {@code value} in
247 * user-defined units. The default implementation returns 1 so that size is
248 * the number of entries and max size is the maximum number of entries.
250 * An entry's size must not change while it is in the cache.
252 protected int sizeOf(final K key, final V value) {
257 * Clear the cache, calling {@link #entryRemoved} on each removed entry.
259 public final void evictAll() {
260 trimToSize(-1); // -1 will evict 0-sized elements
264 * For caches that do not override {@link #sizeOf}, this returns the number
265 * of entries in the cache. For all other caches, this returns the sum of
266 * the sizes of the entries in this cache.
268 public synchronized final int size() {
273 * For caches that do not override {@link #sizeOf}, this returns the maximum
274 * number of entries in the cache. For all other caches, this returns the
275 * maximum sum of the sizes of the entries in this cache.
277 public synchronized final int maxSize() {
282 * Returns the number of times {@link #get} returned a value.
284 public synchronized final int hitCount() {
285 return this.hitCount;
289 * Returns the number of times {@link #get} returned null or required a new
290 * value to be created.
292 public synchronized final int missCount() {
293 return this.missCount;
297 * Returns the number of times {@link #create(Object)} returned a value.
299 public synchronized final int createCount() {
300 return this.createCount;
304 * Returns the number of times {@link #put} was called.
306 public synchronized final int putCount() {
307 return this.putCount;
311 * Returns the number of values that have been evicted.
313 public synchronized final int evictionCount() {
314 return this.evictionCount;
318 * Returns a copy of the current contents of the cache, ordered from least
319 * recently accessed to most recently accessed.
321 public synchronized final Map<K, V> snapshot() {
322 return new LinkedHashMap<K, V>(this.map);
325 @SuppressLint("DefaultLocale")
327 public synchronized final String toString() {
328 final int accesses = this.hitCount + this.missCount;
329 final int hitPercent = accesses != 0 ? 100 * this.hitCount / accesses : 0;
330 return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", this.maxSize,
331 this.hitCount, this.missCount, hitPercent);