package edu.stanford.nlp.sempre.cache; import fig.basic.MemUsage; import java.util.LinkedHashMap; import java.util.Map; /** * TODO(rf): Fig.MemUsage is buggy wrt computing LinkedHashMap byte * usage, so we do our own bookkeeping here, but we are doing so very * incompletely (i.e. only on put() and remove()). * * @author Roy Frostig */ public class LruMap<K, V> extends LinkedHashMap<K, V> { private final int cap; private final LruCallback<K, V> callback; private int bytes = 0; public LruMap(int capacity) { this(capacity, null); } public LruMap(int capacity, LruCallback<K, V> evictCallback) { // "As a general rule, the default load factor (.75) offers a good // tradeoff between time and space costs." // -- Java 8 API, // http://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html super(capacity, 0.75f, true); // Flag true for access-order. this.cap = capacity; this.callback = evictCallback; } public int getCapacity() { return cap; } public int getBytes() { return bytes; } @Override public V put(K key, V value) { boolean replacing = containsKey(key); V old = super.get(key); bytes += MemUsage.getBytes(value); if (replacing) { bytes -= MemUsage.getBytes(old); } else { bytes += MemUsage.getBytes(key); } return super.put(key, value); } @Override public V remove(Object key) { boolean decr = containsKey(key); V old = super.remove(key); if (decr) { bytes -= MemUsage.getBytes(key); bytes -= MemUsage.getBytes(old); } return old; } /** * Ignore the argument, iterate in access order and remove until * memory constraints are satisfied. Always return false, since * we did our own removal. */ @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { while (getBytes() > cap && !isEmpty()) { Map.Entry<K, V> toRemove = null; for (Map.Entry<K, V> entry : entrySet()) { toRemove = entry; break; } remove(toRemove.getKey()); if (callback != null) callback.onEvict(toRemove); } return false; } }