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      *
     91      * @param maxSize The new maximum size.
     92      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    281     protected V create(K key) {
    282         return null;
    283     }
    284 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    348     public synchronized final int createCount() {
    349         return createCount;
    350     }
    351 
    352     /**
    353      * Returns the number of times {@link #put} was called.
    354      */
    355     public synchronized final int putCount() {
    356         return putCount;
    357     }
    358 
    359     /**
    360      * Returns the number of values that have been evicted.
    361      */
    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      */
    370     public synchronized final Map<K, V> snapshot() {
    371         return new LinkedHashMap<K, V>(map);
    372     }
    373 
    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