package DataStructures.Caches;
import java.util.HashMap;
import java.util.Map;
/**
* Most recently used (MRU)
*
* In contrast to Least Recently Used (LRU), MRU discards the most recently used items first.
* https://en.wikipedia.org/wiki/Cache_replacement_policies#Most_recently_used_(MRU)
*
* @param key type
* @param value type
*/
public class MRUCache {
private final Map> data = new HashMap<>();
private Entry head;
private Entry tail;
private int cap;
private static final int DEFAULT_CAP = 100;
public MRUCache() {
setCapacity(DEFAULT_CAP);
}
private void setCapacity(int newCapacity) {
checkCapacity(newCapacity);
for (int i = data.size(); i > newCapacity; i--) {
Entry evicted = evict();
data.remove(evicted.getKey());
}
this.cap = newCapacity;
}
private void checkCapacity(int capacity) {
if (capacity <= 0) {
throw new RuntimeException("capacity must greater than 0!");
}
}
private Entry evict() {
if (head == null) {
throw new RuntimeException("cache cannot be empty!");
}
final Entry evicted = this.tail;
tail = evicted.getPreEntry();
tail.setNextEntry(null);
evicted.setNextEntry(null);
return evicted;
}
public MRUCache(int cap) {
setCapacity(cap);
}
public V get(K key) {
if (!data.containsKey(key)) {
return null;
}
final Entry entry = data.get(key);
moveEntryToLast(entry);
return entry.getValue();
}
public void put(K key, V value) {
if (data.containsKey(key)) {
final Entry exitingEntry = data.get(key);
exitingEntry.setValue(value);
moveEntryToLast(exitingEntry);
return;
}
Entry newEntry;
if (data.size() == cap) {
newEntry = evict();
data.remove(newEntry.getKey());
} else {
newEntry = new Entry<>();
}
newEntry.setKey(key);
newEntry.setValue(value);
addNewEntry(newEntry);
data.put(key, newEntry);
}
private void addNewEntry(Entry newEntry) {
if (data.isEmpty()) {
head = newEntry;
tail = newEntry;
return;
}
tail.setNextEntry(newEntry);
newEntry.setPreEntry(tail);
newEntry.setNextEntry(null);
tail = newEntry;
}
private void moveEntryToLast(Entry entry) {
if (tail == entry) {
return;
}
final Entry preEntry = entry.getPreEntry();
final Entry nextEntry = entry.getNextEntry();
if (preEntry != null) {
preEntry.setNextEntry(nextEntry);
}
if (nextEntry != null) {
nextEntry.setPreEntry(preEntry);
}
if (head == entry) {
head = nextEntry;
}
tail.setNextEntry(entry);
entry.setPreEntry(tail);
entry.setNextEntry(null);
tail = entry;
}
static final class Entry {
private Entry preEntry;
private Entry nextEntry;
private I key;
private J value;
public Entry() {
}
public Entry(Entry preEntry, Entry nextEntry, I key, J value) {
this.preEntry = preEntry;
this.nextEntry = nextEntry;
this.key = key;
this.value = value;
}
public Entry getPreEntry() {
return preEntry;
}
public void setPreEntry(Entry preEntry) {
this.preEntry = preEntry;
}
public Entry getNextEntry() {
return nextEntry;
}
public void setNextEntry(Entry nextEntry) {
this.nextEntry = nextEntry;
}
public I getKey() {
return key;
}
public void setKey(I key) {
this.key = key;
}
public J getValue() {
return value;
}
public void setValue(J value) {
this.value = value;
}
}
public static void main(String[] args) {
final MRUCache cache = new MRUCache<>(2);
cache.put("Key1", 1);
cache.put("Key2", 2);
cache.put("Key3", 3);
cache.put("Key4", 4);
System.out.println("getValue(Key1): " + cache.get("Key1"));
System.out.println("getValue(Key2): " + cache.get("Key2"));
System.out.println("getValue(Key3): " + cache.get("Key3"));
System.out.println("getValue(Key4): " + cache.get("Key4"));
}
}