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