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