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")); } }