blob: fdc25d7c1f884f101c6529337c1bc5ca889de06e [file] [log] [blame]
package com.bumptech.glide.util;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A general purpose size limited cache that evicts items using an LRU algorithm. By default every item is assumed to
* have a size of one. Subclasses can override {@link #getSize(Object)}} to change the size on a per item basis.
*
* @param <T> The type of the keys.
* @param <Y> The type of the values.
*/
public class LruCache<T, Y> {
private final LinkedHashMap<T, Y> cache = new LinkedHashMap<T, Y>(100, 0.75f, true);
private int maxSize;
private final int initialMaxSize;
private int currentSize = 0;
/**
* Constructor for LruCache.
*
* @param size The maximum size of the cache, the units must match the units used in {@link #getSize(Object)}.
*/
public LruCache(int size) {
this.initialMaxSize = size;
this.maxSize = size;
}
/**
* Sets a size multiplier that will be applied to the size provided in the constructor to set the new size of the
* cache. If the new size is less than the current size, entries will be evicted until the current size is less
* than or equal to the new size.
*
* @param multiplier The multiplier to apply.
*/
public void setSizeMultiplier(float multiplier) {
if (multiplier < 0) {
throw new IllegalArgumentException("Multiplier must be >= 0");
}
maxSize = Math.round(initialMaxSize * multiplier);
evict();
}
/**
* Returns the size of a given item, defaulting to one. The units must match those used in the size passed in to the
* constructor. Subclasses can override this method to return sizes in various units, usually bytes.
*
* @param item The item to get the size of.
*/
protected int getSize(Y item) {
return 1;
}
/**
* A callback called whenever an item is evicted from the cache. Subclasses can override.
*
* @param key The key of the evicted item.
* @param item The evicted item.
*/
protected void onItemEvicted(T key, Y item) {
// optional override
}
/**
* Returns the current maximum size of the cache in bytes.
*/
public int getMaxSize() {
return maxSize;
}
/**
* Returns the sum of the sizes of all items in the cache.
*/
public int getCurrentSize() {
return currentSize;
}
/**
* Returns true if there is a value for the given key in the cache.
*
* @param key The key to check.
*/
public boolean contains(T key) {
return cache.containsKey(key);
}
/**
* Returns the item in the cache for the given key or null if no such item exists.
*
* @param key The key to check.
*/
public Y get(T key) {
return cache.get(key);
}
/**
* Adds the given item to the cache with the given key and returns any previous entry for the given key that may
* have already been in the cache.
*
* <p>
* If the size of the item is larger than the total cache size, the item will not be added to the cache and
* instead {@link #onItemEvicted(Object, Object)} will be called synchronously with the given key and item.
* </p>
*
* @param key The key to add the item at.
* @param item The item to add.
*/
public Y put(T key, Y item) {
final int itemSize = getSize(item);
if (itemSize >= maxSize) {
onItemEvicted(key, item);
return null;
}
final Y result = cache.put(key, item);
if (item != null) {
currentSize += getSize(item);
}
if (result != null) {
// TODO: should we call onItemEvicted here?
currentSize -= getSize(result);
}
evict();
return result;
}
/**
* Removes the item at the given key and returns the removed item if present, and null otherwise.
*
* @param key The key to remove the item at.
*/
public Y remove(T key) {
final Y value = cache.remove(key);
if (value != null) {
currentSize -= getSize(value);
}
return value;
}
/**
* Clears all items in the cache.
*/
public void clearMemory() {
trimToSize(0);
}
/**
* Removes the least recently used items from the cache until the current size is less than the given size.
*
* @param size The size the cache should be less than.
*/
protected void trimToSize(int size) {
Map.Entry<T, Y> last;
while (currentSize > size) {
last = cache.entrySet().iterator().next();
final Y toRemove = last.getValue();
currentSize -= getSize(toRemove);
final T key = last.getKey();
cache.remove(key);
onItemEvicted(key, toRemove);
}
}
private void evict() {
trimToSize(maxSize);
}
}