19aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Juddpackage com.bumptech.glide.util;
29aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
39aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Juddimport java.util.LinkedHashMap;
49aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Juddimport java.util.Map;
59aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
69aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd/**
79aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd * A general purpose size limited cache that evicts items using an LRU algorithm. By default every item is assumed to
89aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd * have a size of one. Subclasses can override {@link #getSize(Object)}} to change the size on a per item basis.
99aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd *
109aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd * @param <T> The type of the keys.
119aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd * @param <Y> The type of the values.
129aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd */
139aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Juddpublic class LruCache<T, Y> {
149aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    private final LinkedHashMap<T, Y> cache = new LinkedHashMap<T, Y>(100, 0.75f, true);
15d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd    private int maxSize;
16d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd    private final int initialMaxSize;
179aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    private int currentSize = 0;
189aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
195f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
205f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Constructor for LruCache.
215f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
225f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param size The maximum size of the cache, the units must match the units used in {@link #getSize(Object)}.
235f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
249aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    public LruCache(int size) {
25d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd        this.initialMaxSize = size;
269aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        this.maxSize = size;
279aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
289aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
295f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
305f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Sets a size multiplier that will be applied to the size provided in the constructor to set the new size of the
315f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * cache. If the new size is less than the current size, entries will be evicted until the current size is less
325f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * than or equal to the new size.
335f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
345f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param multiplier The multiplier to apply.
355f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
36d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd    public void setSizeMultiplier(float multiplier) {
37d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd        if (multiplier < 0) {
38d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd            throw new IllegalArgumentException("Multiplier must be >= 0");
39d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd        }
40d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd        maxSize = Math.round(initialMaxSize * multiplier);
41d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd        evict();
42d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd    }
43d8900c3aa1844ac66a1019eefd368c83459c2c4cSam Judd
445f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
455f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Returns the size of a given item, defaulting to one. The units must match those used in the size passed in to the
465f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * constructor. Subclasses can override this method to return sizes in various units, usually bytes.
475f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
485f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param item The item to get the size of.
495f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
509aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    protected int getSize(Y item) {
519aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        return 1;
529aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
539aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
545f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
555f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * A callback called whenever an item is evicted from the cache. Subclasses can override.
565f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
575f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param key The key of the evicted item.
585f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param item The evicted item.
595f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
60de24d6a2112bebef8d42def8c1f21c79ab7d418fRobert Papp    protected void onItemEvicted(T key, Y item) {
61de24d6a2112bebef8d42def8c1f21c79ab7d418fRobert Papp        // optional override
62de24d6a2112bebef8d42def8c1f21c79ab7d418fRobert Papp    }
639aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
645f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
655207906814e7f5983ce16db79affba28e5651d2eSam Judd     * Returns the current maximum size of the cache in bytes.
665207906814e7f5983ce16db79affba28e5651d2eSam Judd     */
675207906814e7f5983ce16db79affba28e5651d2eSam Judd    public int getMaxSize() {
685207906814e7f5983ce16db79affba28e5651d2eSam Judd        return maxSize;
695207906814e7f5983ce16db79affba28e5651d2eSam Judd    }
705207906814e7f5983ce16db79affba28e5651d2eSam Judd
715207906814e7f5983ce16db79affba28e5651d2eSam Judd    /**
725f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Returns the sum of the sizes of all items in the cache.
735f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
749aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    public int getCurrentSize() {
759aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        return currentSize;
769aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
779aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
785f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
795f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Returns true if there is a value for the given key in the cache.
805f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
815f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param key The key to check.
825f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
835f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd
849aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    public boolean contains(T key) {
859aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        return cache.containsKey(key);
869aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
879aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
885f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
895f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Returns the item in the cache for the given key or null if no such item exists.
905f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
915f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param key The key to check.
925f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
939aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    public Y get(T key) {
949aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        return cache.get(key);
959aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
969aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
975f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
985f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Adds the given item to the cache with the given key and returns any previous entry for the given key that may
995f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * have already been in the cache.
1005f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
1015f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * <p>
1025f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *     If the size of the item is larger than the total cache size, the item will not be added to the cache and
1035f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *     instead {@link #onItemEvicted(Object, Object)} will be called synchronously with the given key and item.
1045f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * </p>
1055f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
1065f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param key The key to add the item at.
1075f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param item The item to add.
1085f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
1099aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    public Y put(T key, Y item) {
1100e2e2b1b8df449b6e3223b090f5a55f1993e6c1fSam Judd        final int itemSize = getSize(item);
1110e2e2b1b8df449b6e3223b090f5a55f1993e6c1fSam Judd        if (itemSize >= maxSize) {
1125f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd            onItemEvicted(key, item);
1130e2e2b1b8df449b6e3223b090f5a55f1993e6c1fSam Judd            return null;
1140e2e2b1b8df449b6e3223b090f5a55f1993e6c1fSam Judd        }
1150e2e2b1b8df449b6e3223b090f5a55f1993e6c1fSam Judd
1169aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        final Y result = cache.put(key, item);
1175ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd        if (item != null) {
1188ff32510d6572ca4952a87ccb8ad7140c1619443Sam Judd            currentSize += getSize(item);
1198ff32510d6572ca4952a87ccb8ad7140c1619443Sam Judd        }
1205ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd        if (result != null) {
1215ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd            // TODO: should we call onItemEvicted here?
1225ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd            currentSize -= getSize(result);
1235ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd        }
1245ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd        evict();
1255ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd
1269aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        return result;
1279aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
1289aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
1295f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
1305f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Removes the item at the given key and returns the removed item if present, and null otherwise.
1315f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
1325f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param key The key to remove the item at.
1335f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
134e743a1f03f24e33270f38de883b508d4312a7f69Sam Judd    public Y remove(T key) {
135e743a1f03f24e33270f38de883b508d4312a7f69Sam Judd        final Y value = cache.remove(key);
136e743a1f03f24e33270f38de883b508d4312a7f69Sam Judd        if (value != null) {
137e743a1f03f24e33270f38de883b508d4312a7f69Sam Judd            currentSize -= getSize(value);
138e743a1f03f24e33270f38de883b508d4312a7f69Sam Judd        }
139e743a1f03f24e33270f38de883b508d4312a7f69Sam Judd        return value;
140e743a1f03f24e33270f38de883b508d4312a7f69Sam Judd    }
141e743a1f03f24e33270f38de883b508d4312a7f69Sam Judd
1425f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
1435f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Clears all items in the cache.
1445f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
1459aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    public void clearMemory() {
1469aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        trimToSize(0);
1479aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
1489aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
1495f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd    /**
1505f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * Removes the least recently used items from the cache until the current size is less than the given size.
1515f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     *
1525f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     * @param size The size the cache should be less than.
1535f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd     */
1549aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    protected void trimToSize(int size) {
1559aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        Map.Entry<T, Y> last;
1569aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        while (currentSize > size) {
1579aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd            last = cache.entrySet().iterator().next();
1589aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd            final Y toRemove = last.getValue();
1599aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd            currentSize -= getSize(toRemove);
16078bad2aa32f824f9e098b5058dfa3506a7ed3f62Sam Judd            final T key = last.getKey();
16178bad2aa32f824f9e098b5058dfa3506a7ed3f62Sam Judd            cache.remove(key);
1625f4610b54d517be58105bcf73ce3291ba79f9f40Sam Judd            onItemEvicted(key, toRemove);
1639aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        }
1649aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
1659aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd
1669aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    private void evict() {
1679aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd        trimToSize(maxSize);
1689aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd    }
1699aa6dd1e9e9497e54d38a8f5f686dd510b224ee1Sam Judd}
170