1 package com.bumptech.glide.util; 2 3 import java.util.LinkedHashMap; 4 import java.util.Map; 5 6 /** 7 * A general purpose size limited cache that evicts items using an LRU algorithm. By default every item is assumed to 8 * have a size of one. Subclasses can override {@link #getSize(Object)}} to change the size on a per item basis. 9 * 10 * @param <T> The type of the keys. 11 * @param <Y> The type of the values. 12 */ 13 public class LruCache<T, Y> { 14 private final LinkedHashMap<T, Y> cache = new LinkedHashMap<T, Y>(100, 0.75f, true); 15 private int maxSize; 16 private final int initialMaxSize; 17 private int currentSize = 0; 18 19 /** 20 * Constructor for LruCache. 21 * 22 * @param size The maximum size of the cache, the units must match the units used in {@link #getSize(Object)}. 23 */ 24 public LruCache(int size) { 25 this.initialMaxSize = size; 26 this.maxSize = size; 27 } 28 29 /** 30 * Sets a size multiplier that will be applied to the size provided in the constructor to set the new size of the 31 * cache. If the new size is less than the current size, entries will be evicted until the current size is less 32 * than or equal to the new size. 33 * 34 * @param multiplier The multiplier to apply. 35 */ 36 public void setSizeMultiplier(float multiplier) { 37 if (multiplier < 0) { 38 throw new IllegalArgumentException("Multiplier must be >= 0"); 39 } 40 maxSize = Math.round(initialMaxSize * multiplier); 41 evict(); 42 } 43 44 /** 45 * Returns the size of a given item, defaulting to one. The units must match those used in the size passed in to the 46 * constructor. Subclasses can override this method to return sizes in various units, usually bytes. 47 * 48 * @param item The item to get the size of. 49 */ 50 protected int getSize(Y item) { 51 return 1; 52 } 53 54 /** 55 * A callback called whenever an item is evicted from the cache. Subclasses can override. 56 * 57 * @param key The key of the evicted item. 58 * @param item The evicted item. 59 */ 60 protected void onItemEvicted(T key, Y item) { 61 // optional override 62 } 63 64 /** 65 * Returns the current maximum size of the cache in bytes. 66 */ 67 public int getMaxSize() { 68 return maxSize; 69 } 70 71 /** 72 * Returns the sum of the sizes of all items in the cache. 73 */ 74 public int getCurrentSize() { 75 return currentSize; 76 } 77 78 /** 79 * Returns true if there is a value for the given key in the cache. 80 * 81 * @param key The key to check. 82 */ 83 84 public boolean contains(T key) { 85 return cache.containsKey(key); 86 } 87 88 /** 89 * Returns the item in the cache for the given key or null if no such item exists. 90 * 91 * @param key The key to check. 92 */ 93 public Y get(T key) { 94 return cache.get(key); 95 } 96 97 /** 98 * Adds the given item to the cache with the given key and returns any previous entry for the given key that may 99 * have already been in the cache. 100 * 101 * <p> 102 * If the size of the item is larger than the total cache size, the item will not be added to the cache and 103 * instead {@link #onItemEvicted(Object, Object)} will be called synchronously with the given key and item. 104 * </p> 105 * 106 * @param key The key to add the item at. 107 * @param item The item to add. 108 */ 109 public Y put(T key, Y item) { 110 final int itemSize = getSize(item); 111 if (itemSize >= maxSize) { 112 onItemEvicted(key, item); 113 return null; 114 } 115 116 final Y result = cache.put(key, item); 117 if (item != null) { 118 currentSize += getSize(item); 119 } 120 if (result != null) { 121 // TODO: should we call onItemEvicted here? 122 currentSize -= getSize(result); 123 } 124 evict(); 125 126 return result; 127 } 128 129 /** 130 * Removes the item at the given key and returns the removed item if present, and null otherwise. 131 * 132 * @param key The key to remove the item at. 133 */ 134 public Y remove(T key) { 135 final Y value = cache.remove(key); 136 if (value != null) { 137 currentSize -= getSize(value); 138 } 139 return value; 140 } 141 142 /** 143 * Clears all items in the cache. 144 */ 145 public void clearMemory() { 146 trimToSize(0); 147 } 148 149 /** 150 * Removes the least recently used items from the cache until the current size is less than the given size. 151 * 152 * @param size The size the cache should be less than. 153 */ 154 protected void trimToSize(int size) { 155 Map.Entry<T, Y> last; 156 while (currentSize > size) { 157 last = cache.entrySet().iterator().next(); 158 final Y toRemove = last.getValue(); 159 currentSize -= getSize(toRemove); 160 final T key = last.getKey(); 161 cache.remove(key); 162 onItemEvicted(key, toRemove); 163 } 164 } 165 166 private void evict() { 167 trimToSize(maxSize); 168 } 169 } 170