wake-up-neo.com

LRU-Cache in Java mit Generics und O(1) Operationen

Dies ist eine Frage, die in Bewerbungsgesprächen häufig gestellt wird. Die Idee ist, eine Datenstruktur zu definieren, anstatt die in LinkedHashMap eingebaute Java zu verwenden.

Ein LRU-Cache löscht den Eintrag least least used , um einen neuen Eintrag einzufügen . In folgendem Szenario:

 A - B - C - D - E

Wenn A der am wenigsten kürzlich verwendete Gegenstand ist, müssen wir A entfernen, wenn wir F einfügen möchten.

Dies kann leicht implementiert werden, wenn eine HashMap mit den Cache-Einträgen nach (Schlüssel, Wert) und einer separaten Liste aufbewahrt wird, die den Schlüssel und die Nutzungszeit der Elemente enthält. Wir müssten jedoch die Liste abfragen, um das am wenigsten kürzlich verwendete Element mit einer möglichen O(n) Zeitkomplexität zu finden.

Wie kann diese Struktur in Java für generische Objekte und O(1) - Operationen implementiert werden?

Dies unterscheidet sich vom möglichen Duplikat darin, dass es sich auf die Effizienz (O (1) ops) und die Implementierung der Datenstruktur selbst konzentriert, nicht auf Javas Erweiterung.

31
liarspocker

Die Frage selbst zeigt, dass das Problem der O(n) - Operationen beim Abfragen der verknüpften Liste auftritt. Daher benötigen wir eine alternative Datenstruktur. Wir müssen in der Lage sein, die letzte Zugriffszeit der Elemente über die HashMap ohne Suche zu aktualisieren.

Wir können zwei separate Datenstrukturen beibehalten. Eine HashMap mit (Key, Pointer) pairs und einer doppelt verknüpften Liste , die als Prioritätswarteschlange zum Löschen und Speichern der Werte dient. Von der HashMap aus können wir auf ein Element in der doppelt verknüpften Liste verweisen und die Abrufzeit aktualisieren. Da wir direkt von der HashMap zu dem Element in der Liste gelangen, bleibt unsere zeitliche Komplexität bei O (1).

Unsere doppelt verknüpfte Liste kann beispielsweise so aussehen:

least_recently_used  -> A <-> B <-> C <-> D <-> E <- most_recently_used

Wir müssen einen Zeiger auf die LRU- und MRU-Elemente halten. Die Werte der Einträge werden in der Liste gespeichert und wenn wir die HashMap abfragen, erhalten wir einen Zeiger auf die Liste. Bei get () müssen wir das Element ganz rechts in der Liste platzieren. Wenn der Cache voll ist (Schlüssel, Wert), müssen wir das Element ganz links in der Liste sowohl aus der Liste als auch aus der HashMap entfernen.

Das folgende Beispiel zeigt eine Implementierung in Java:

public class LRUCache<K, V>{

    // Define Node with pointers to the previous and next items and a key, value pair
    class Node<T, U> {
        Node<T, U> previous;
        Node<T, U> next;
        T key;
        U value;

        public Node(Node<T, U> previous, Node<T, U> next, T key, U value){
            this.previous = previous;
            this.next = next;
            this.key = key;
            this.value = value;
        }
    }

    private HashMap<K, Node<K, V>> cache;
    private Node<K, V> leastRecentlyUsed;
    private Node<K, V> mostRecentlyUsed;
    private int maxSize;
    private int currentSize;

    public LRUCache(int maxSize){
        this.maxSize = maxSize;
        this.currentSize = 0;
        leastRecentlyUsed = new Node<K, V>(null, null, null, null);
        mostRecentlyUsed = leastRecentlyUsed;
        cache = new HashMap<K, Node<K, V>>();
    }

    public V get(K key){
        Node<K, V> tempNode = cache.get(key);
        if (tempNode == null){
            return null;
        }
        // If MRU leave the list as it is
        else if (tempNode.key == mostRecentlyUsed.key){
            return mostRecentlyUsed.value;
        }

        // Get the next and previous nodes
        Node<K, V> nextNode = tempNode.next;
        Node<K, V> previousNode = tempNode.previous;

        // If at the left-most, we update LRU 
        if (tempNode.key == leastRecentlyUsed.key){
            nextNode.previous = null;
            leastRecentlyUsed = nextNode;
        }

        // If we are in the middle, we need to update the items before and after our item
        else if (tempNode.key != mostRecentlyUsed.key){
            previousNode.next = nextNode;
            nextNode.previous = previousNode;
        }

        // Finally move our item to the MRU
        tempNode.previous = mostRecentlyUsed;
        mostRecentlyUsed.next = tempNode;
        mostRecentlyUsed = tempNode;
        mostRecentlyUsed.next = null;

        return tempNode.value;

    }

    public void put(K key, V value){
        if (cache.containsKey(key)){
            return;
        }

        // Put the new node at the right-most end of the linked-list
        Node<K, V> myNode = new Node<K, V>(mostRecentlyUsed, null, key, value);
        mostRecentlyUsed.next = myNode;
        cache.put(key, myNode);
        mostRecentlyUsed = myNode;

        // Delete the left-most entry and update the LRU pointer
        if (currentSize == maxSize){
            cache.remove(leastRecentlyUsed.key);
            leastRecentlyUsed = leastRecentlyUsed.next;
            leastRecentlyUsed.previous = null;
        }

        // Update cache size, for the first added entry update the LRU pointer
        else if (currentSize < maxSize){
            if (currentSize == 0){
                leastRecentlyUsed = myNode;
            }
            currentSize++;
        }
    }
}
47
liarspocker

Implementierung, die die Tests des leetcode questiton bestanden hat. + Einfache Unit-Tests folgen.

Ich habe eine Pull-Anfrage an folgende Adresse gestellt: https://github.com/haoel/leetcode/pull/90/files

LRUCache.Java

import Java.util.LinkedHashMap;
import Java.util.Iterator;

public class LRUCache {

    private int capacity;
    private LinkedHashMap<Integer,Integer> map;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.map = new LinkedHashMap<>();
    }

    public int get(int key) {
        Integer value = this.map.get(key);
        if (value == null) {
            value = -1;
        } else {
            this.set(key, value);
        }
        return value;
    }

    public void set(int key, int value) {
        if (this.map.containsKey(key)) {
            this.map.remove(key);
        } else if (this.map.size() == this.capacity) {
            Iterator<Integer> it = this.map.keySet().iterator();
            it.next();
            it.remove();
        }
        map.put(key, value);
    }
}

LRUCacheTest.Java:

import Java.util.ArrayList;

import org.junit.Test;
import static org.junit.Assert.*;

public class LRUCacheTest {

    private LRUCache c;

    public LRUCacheTest() {
        this.c = new LRUCache(2);
    }

    @Test
    public void testCacheStartsEmpty() {
        assertEquals(c.get(1), -1);
    }

    @Test
    public void testSetBelowCapacity() {
        c.set(1, 1);
        assertEquals(c.get(1), 1);
        assertEquals(c.get(2), -1);
        c.set(2, 4);
        assertEquals(c.get(1), 1);
        assertEquals(c.get(2), 4);
    }

    @Test
    public void testCapacityReachedOldestRemoved() {
        c.set(1, 1);
        c.set(2, 4);
        c.set(3, 9);
        assertEquals(c.get(1), -1);
        assertEquals(c.get(2), 4);
        assertEquals(c.get(3), 9);
    }

    @Test
    public void testGetRenewsEntry() {
        c.set(1, 1);
        c.set(2, 4);
        assertEquals(c.get(1), 1);
        c.set(3, 9);
        assertEquals(c.get(1), 1);
        assertEquals(c.get(2), -1);
        assertEquals(c.get(3), 9);
    }
}

removeEldestEntry () alternative Implementierung

Ich bin nicht sicher, dass es sich lohnt, da es dieselbe Anzahl von Zeilen benötigt, aber hier geht es zum Abschluss:

import Java.util.LinkedHashMap;
import Java.util.Iterator;
import Java.util.Map;

class LinkedhashMapWithCapacity<K,V> extends LinkedHashMap<K,V> {
    private int capacity;

    public LinkedhashMapWithCapacity(int capacity) {
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return this.size() > this.capacity;
    }
}

public class LRUCache {

    private LinkedhashMapWithCapacity<Integer,Integer> map;

    public LRUCache(int capacity) {
        this.map = new LinkedhashMapWithCapacity<>(capacity);
    }

    public int get(int key) {
        Integer value = this.map.get(key);
        if (value == null) {
            value = -1;
        } else {
            this.set(key, value);
        }
        return value;
    }

    public void set(int key, int value) {
        if (this.map.containsKey(key))
            this.map.remove(key);
        this.map.get(key);
    }
}

Die LinkedHashMap wurde mit diesem Gedanken entwickelt

Aus den Javadocs:

Es wird ein spezieller Konstruktor bereitgestellt, um eine verknüpfte Hash-Map zu erstellen, in der die Reihenfolge der Iteration die Reihenfolge ist, in der zuletzt auf die Einträge zugegriffen wurde, vom letzten Zugriff bis zum letzten (Zugriffsreihenfolge). Diese Art von Karte eignet sich gut zum Erstellen von LRU-Caches. Das Aufrufen der Methoden put, putIfAbsent, get, getOrDefault, computeIfAbsent, computeIfPresent oder merge führt zu einem Zugriff auf den entsprechenden Eintrag (vorausgesetzt, er existiert nach Abschluss des Aufrufs). Die Ersetzungsmethoden führen nur zu einem Zugriff auf den Eintrag, wenn der Wert ersetzt wird. Die putAll-Methode generiert für jedes Mapping in der angegebenen Map einen Eintragszugriff in der Reihenfolge, in der Schlüsselwertzuordnungen vom Iterator für den angegebenen Satz der Map bereitgestellt werden. Keine anderen Methoden generieren Eintrittszugriffe. Insbesondere beeinflussen Vorgänge für Sammlungsansichten die Reihenfolge der Iteration der Hintergrundkarte nicht.

Die removeEldestEntry (Map.Entry) -Methode kann überschrieben werden, um eine .__ zu erzwingen. Richtlinie zum automatischen Entfernen veralteter Zuordnungen, wenn neue Zuordnungen .__ sind. zur Karte hinzugefügt.

11
K''

Stack und HashMap verwenden:

import Java.util.HashMap;
import Java.util.Stack;
public class LRU {
    private HashMap<String,Object> lruList;
    private Stack<String> stackOrder;
    private int capacity;
    LRU(int capacity){
      this.capacity = capacity;
      this.lruList = new HashMap<String, Object>(capacity);
      this.stackOrder = new Stack<String>();
    }
    public void put(String key, Object value){
      if(lruList.containsKey(key) || this.capacity < lruList.size() + 1) {
        if(lruList.containsKey(key)){
            String remove = key;
        }else{
            String remove = this.stackOrder.firstElement();
        }
        this.stackOrder.removeElement(remove);
        this.lruList.remove(remove);
      }
      this.lruList.put(key, value);
      this.stackOrder.add(key);
    }
    public Stack<String> get(){
      return this.stackOrder;
    }
    public Object get(String key){
      Object value = lruList.get(key);
      put(key, value);
      return value;
    }
}

public static void main(String[] args) {
    LRU lru = new LRU(3);
    lru.put("k1","v1");
    lru.put("k2","v2");
    lru.put("k3","v3");
    System.out.println(lru.get());
    lru.get("k1");
    System.out.println(lru.get());
    lru.put("k4","v4");
    System.out.println(lru.get());
}

Ausgabe:

[k1, k2, k3]

[k2, k3, k1]

[k3, k1, k4]

5
Gil Beyruth
/*Java implementation using Deque and HashMap    */

interface Cache<K,V> {
    V get(K key) ;
    void set(K key, V value);
}

class Node <K,V>{
    K key;
    V value;

    public Node (K key, V value) {
        this.key = key;
        this.value = value;
    }
}

public class LRUCache <K,V> implements Cache<K,V>{
    Deque<Node<K,V>> dq = new LinkedList<>();
    Map<K, Node<K, V>> map = new HashMap<>();
    static int SIZE;

    public LRUCache(int size) {
        this.SIZE = size;
    }

    public V get(K key) {
        Node<K,V> result = map.get(key);
        if(result != null) {
            dq.remove(result);
            dq.addFirst(result);
            System.out.println("Get " +key +" : " +result.value);
            return result.value;
        }
        else {
            System.out.println("Cache miss");
            return null;
        }
    }

    public void set(K key, V value) {
        System.out.println("Set " +key +" : " +value);
        Node<K,V> result = map.get(key);
        if(result != null) {
            result.value = value;
            map.put(key, result);
            dq.remove(result);
            dq.addFirst(result);
            System.out.println("Updated frame " +key+" as " + value);
        }
        else {
            if(dq.size() == SIZE) {
                Node<K,V> toRemove = dq.removeLast();
                map.remove(toRemove);
                System.out.println("Frame removed " +toRemove.key +" : " +toRemove.value);
            }
            Node<K,V> newNode = new Node(key, value);
            dq.addFirst(newNode);
            map.put(key, newNode);
            System.out.println("Frame added " + key + " : " +value);
        }
    }

    public static void main(String[] args) {
        Cache<Integer, Integer> lru = new LRUCache<>(5);
        lru.get(2);
        lru.set(1, 11);
        lru.set(2, 22);
        lru.get(2);
        lru.set(3, 33);
        lru.set(4, 44);
        lru.set(5, 55);
        lru.get(2);
        lru.get(1);
        lru.set(6, 66);
    }
}
4
Princy Joy

@templatetypedef

public LinkedHashMap(int initialCapacity,
         float loadFactor,
         boolean accessOrder)

accessOrder - der Bestellmodus - true für Zugriffsreihenfolge, false für Einfügungsreihenfolge

2
Droid Teahouse

Ich schreibe einfach diese sehr einfache generische Lösung, obwohl diese nicht Thread sicher ist. 

LRUCacheDemo.Java (öffentliche Klasse)

import Java.io.*;
import Java.util.*;

/* We can keep two separate data structures. A HashMap with (Key,Pointer) pairs and a doubly linked
  list which will work as the priority queue for deletion and store the Values. From the HashMap, 
  we can point to an element in the doubly linked list and update its' retrieval time. Because we
  go directly from the HashMap to the item in the list, our time complexity remains at O(1) 
*/
public class LRUCacheDemo {
    public static void main(String args[]) {
        LRUCache<Integer, Integer> lru = new LRUCache<>(3);
        for(int i=1; i<=9; ++i) {
            lru.put(i, 100*i+10*i+i); //i   iii
        }

        lru.get(8);
        /* for(Map.Entry<Integer, Integer> entry : lru.entrySet()) {
            System.out.printf("\n%1$-5s  %2$-5s", entry.getKey(), entry.getValue());
        } */

        System.out.println("LRU  : " + lru);
    }
} 

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int CACHE_SIZE;

    //NOTE : LinkedHashMap have already given implementation for LRU, this class has just used those method
    //See http://grepcode.com/file/repository.grepcode.com/Java/root/jdk/openjdk/6-b14/Java/util/LinkedHashMap.Java#LinkedHashMap

    // LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
    // accessOrder - to maintain in order of elements from least-recently accessed to most-recently.
    LRUCache(final int sizeIn) {
        super(sizeIn, 0.75F, true);
        this.CACHE_SIZE = sizeIn;
    }

    @Override 
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return size() > this.CACHE_SIZE; 
        /* Returns true if this map should remove its eldest entry. This method is invoked by put and putAll after 
           inserting a new entry into the map. It provides the implementor with the opportunity to remove the eldest 
           entry each time a new one is added. This is useful if the map represents a cache.
        */
    }
}

O/P:  

LRU : {7=777, 9=999, 8=888}
2
roottraveller

Idee

cache ist nichts anderes als kreisförmige ArrayList. Diese Liste enthält den Eintrag . Wenn immer neue Einträge hinzugefügt werden, fügen Sie am Ende der Liste hinzu Das bedeutet, dass das am wenigsten kürzlich verwendete Element beim ersten . Verwendet wird und am Ende hinzufügen.

Um ein Element zu erhalten, müssen wir die Liste durchlaufen, was O(n) Zeitaufwand erfordert. Um dies zu vermeiden, pflege ich HashMap>. Hier ist k Schlüssel und IndexNode enthält einen Zeiger auf den Eintrag in der Liste. so können wir die Zeitkomplexität O(1) erhalten.

arbeitslösung

package lrucache;

import Java.util.HashMap;

public class LRUCache<K, V> {

    private transient Entry<K, V> header = new Entry<K, V>(null, null, null, null);
    public HashMap<K,IndexNode<Entry<K,V>>> indexMap = new HashMap<K,IndexNode<Entry<K,V>>>();
    private final int CACHE_LIMIT = 3;
    private int size;

    public LRUCache() {
        header.next = header.previous = header;
        this.size = 0;
    }

    public void put(K key,V value){
        Entry<K,V> newEntry = new Entry<K,V>(key,value,null,null);
        addBefore(newEntry, header);
    }

    private void addBefore(Entry<K,V> newEntry,Entry<K,V> entry){
        if((size+1)<(CACHE_LIMIT+1)){
            newEntry.next=entry;
            newEntry.previous=entry.previous;
            IndexNode<Entry<K,V>> indexNode = new IndexNode<Entry<K,V>>(newEntry);
            indexMap.put(newEntry.key, indexNode);
            newEntry.previous.next=newEntry;
            newEntry.next.previous=newEntry;
            size++;
        }else{
            Entry<K,V>  entryRemoved = remove(header.next);
            indexMap.remove(entryRemoved.key);
            addBefore(newEntry, entry);
        }
    }

    public void get(K key){
        if(indexMap.containsKey(key)){
            Entry<K,V> newEntry = remove(indexMap.get(key).pointer);
            addBefore(newEntry,header);
        }else{
            System.out.println("No such element was cached. Go and get it from Disk");
        }
    }

    private Entry<K,V> remove(Entry<K,V> entry){
        entry.previous.next=entry.next;
        entry.next.previous = entry.previous;
        size--;
        return entry;
    }

    public void display(){
        for(Entry<K,V> curr=header.next;curr!=header;curr=curr.next){
            System.out.println("key : "+curr.key+" value : " + curr.value);
        }
    }

    private static class IndexNode<Entry>{
        private Entry pointer;
        public IndexNode(Entry pointer){
            this.pointer = pointer;
        }
    }

    private static class Entry<K, V> {
        K key;
        V value;
        Entry<K, V> previous;
        Entry<K, V> next;

        Entry(K key, V value, Entry<K, V> next, Entry<K, V> previous) {
            this.key = key;
            this.value = value;
            this.next = next;
            this.previous = previous;
        }
    }

    public static void main(String[] args) {
        LRUCache<String, Integer> cache = new LRUCache<String, Integer>();
        cache.put("abc", 1);
        //cache.display();
        cache.put("def", 2);
        cache.put("ghi", 3);
        cache.put("xyz", 4);
        cache.put("xab", 5);
        cache.put("xbc", 6);
        cache.get("xyz");
        cache.display();
        System.out.println(cache.indexMap);
    }
}

ausgabe

key : xab value : 5
key : xbc value : 6
key : xyz value : 4
{[email protected], [email protected], [email protected]}
1
Venkat Kondeti

wir können LinkedHashMap verwenden. Es hat eine Funktion, um den ältesten Eintrag zu entfernen 

import Java.util.LinkedHashMap;
import Java.util.Map;

public LRUCache<K, V> extends LinkedHashMap<K, V> {
  private int cacheSize;

  public LRUCache(int cacheSize) {
  super(16, 0.75, true);
  this.cacheSize = cacheSize;
}

  protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
  return size() >= cacheSize;
 }
}

Der einzige Haken ist, dass standardmäßig die Reihenfolge der verknüpften Listen die Reihenfolge der Einfügungen ist und nicht der Zugriff. Ein Konstruktor macht jedoch eine Option verfügbar, die stattdessen die Zugriffsreihenfolge verwendet.

1
Prashant Gautam

Hier ist die Java-Implementierung mit Generics und LinkedHashMap und der Komplexität von O(1) auf get() und put().

import Java.util.LinkedHashMap;

public class LRUCache<K, V> {

    private LinkedHashMap<K, V> lruCacheMap;
    private final int capacity;
    private final boolean SORT_BY_ACCESS = true;
    private final float LOAD_FACTOR = 0.75F;

    public LRUCache(int capacity){
        this.capacity = capacity;
        this.lruCacheMap = new LinkedHashMap<>(capacity, LOAD_FACTOR, SORT_BY_ACCESS);
    }

    public V get(K k){
        return lruCacheMap.get(k);
    }

    public void put(K k, V v){
        if(lruCacheMap.containsKey(k)){
            lruCacheMap.remove(k);
        }else if(lruCacheMap.size() >= capacity){
            lruCacheMap.remove(lruCacheMap.keySet().iterator().next());
        }
        lruCacheMap.put(k, v);
    }

    public void printSequence(){
        System.out.println(lruCacheMap.keySet());
    }
}

Dies ist der Code zum Testen:

import Java.util.Arrays;
import Java.util.HashMap;
import Java.util.Map;

public class TestLruCache {

    static class MyHardDrive {
        Map<String, Object> resources = new HashMap<>();

        MyHardDrive(){
            for(Character i='A'; i<='Z'; i++){
                resources.put(i.toString(), new Object());
            }
        }

        Object loadResource(String name){
            return resources.get(name);
        }
    }

    public static void main(String[] args) {
        MyHardDrive hd = new MyHardDrive();
        LRUCache<String,Object> cache = new LRUCache<>(4);

        for(String key: Arrays.asList("A","B","C","A","D","E","F","E","F","G","A")){
            Object object = cache.get(key);
            if(object==null){
                object = hd.loadResource(key);
                cache.put(key, object);
            }
            cache.printSequence();
        }
    }
}
1
wwesantos
public class LeastRecentlyUsed {

    public static <K, V> Map<K, V> leastRecentlyUsedCache(final int maxSize) {
        return new LinkedHashMap<K, V>(maxSize , 0.7f, true) {
            private static final long serialVersionUID = -3588047435434569014L;
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > maxSize;
            }
        };
    }

    public static void main(String[] args) {
        Map<Object, Object> leastRecentlyUsed = LeastRecentlyUsed.leastRecentlyUsedCache(3);
        leastRecentlyUsed.put("Robert", "Raj");
        leastRecentlyUsed.put("Auzi", "Aiz");
        leastRecentlyUsed.put("Sandy", "S");
        leastRecentlyUsed.put("Tript", "tty");  
        System.out.println(leastRecentlyUsed);
    }
  }
0
12Sandy

Wie K und Others gesagt haben, kann dies mit der LinkedHashMap-Klasse geschehen. Bei einem Squeeze erhalten Sie eine Minimalversion in 15 Zeilen Code.

@Ciro Santilli , warum nicht einfach die zusätzliche Klassendefinition ausschneiden? Wenn die Tests wie bei anderen Java-Maps verwendet werden, müssten wir diese Methode nicht mit einer set-Methode als Alias ​​bezeichnen, was wir tatsächlich erwarten würden, return eine bestimmte Setansicht aus der Map zurückzugeben. 

import Java.utils.LinkedHashMap
import Java.util.Map;

public class LRUCache<K,V> extends LinkedHashMap<K,V> {
    private int maxSize;

    // and other constructors for load factor and hashtable capacity
    public LRUCache(int initialCapacity, float loadFactor, int maxSize) {
        super(initialCapacity, loadFactor, true);
        this.maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return size() > maxSize;
    }

    // I don't like this. set should return a set put should put an item
    public V set(K key, V value) {
        put(key, value)
    }
}

hier und In den Dokumenten für mehr.

0
Richard Mathie

Hier ist die Java-Implementierung 

import Java.util.HashMap;
import Java.util.Map;

import com.nadeem.app.dsa.adt.Cache;
// Kind of linkedHashMap
public class LRUCache <K, V> implements Cache<K, V> {

private int capacity;
private Node<K, V> head, tail;
private Map<K, Node<K, V>> map;

private static final int DEFAULT_CAPACITY = 10;

public LRUCache(int capacity) {
    this.capacity = capacity;
    this.map = new HashMap<K, Node<K,V>>();
}

public LRUCache() {
    this(DEFAULT_CAPACITY);
}

@Override
public V get(K key) {
    V result = null;
    Node<K, V> node = this.map.get(key);
    if (node != null) {
        result = node.value;
        remove(node);
        addAsHead(node);
    }
    return result;
}

@Override
public void set(K key, V value) {
    Node<K, V> node = this.map.get(key);
    if (node == null) {
        Node<K, V> temp = new Node<K, V>(key, value);
        if (this.map.size() < this.capacity) {
            addAsHead(temp);
        } else {
            this.map.remove(this.tail.key);
            remove(this.tail);
            addAsHead(temp);
        }
        this.map.put(key, temp);
    } else {
        node.value = value;
        remove(node);
        addAsHead(node);
    }
}

private void remove(Node<K, V> node) {

    if (node.pre == null) {
        this.head = node.next;
    } else {
        node.pre.next = node.next;
    }

    if (node.next == null) {
        this.tail = node.pre;
    } else {
        node.next.pre = node.pre;
    }       
}

private void addAsHead(Node<K, V> node) {
    if (this.head == null) {
        this.head = node;
        this.tail = node;
    } else {
        this.head.pre = node;
        node.next = this.head;
        this.head = node;
    }
}

@Override
public void remove(K key) {
    Node<K, V> node = this.map.get(key);
    if (node != null) {
        this.remove(node);
    }       
}

private static class Node <S, T> {
    public S key;
    public T value;
    Node<S, T> pre;
    Node<S, T> next;

    Node(S key, T value) {
        this.key = key;
        this.value = value;
    }       
}

@Override
public int size() {
    return this.map.size();
}

}

Hier ist der Unit-Test

public class LRUCacheTest {

private LRUCache<Integer, Integer> cache;

@Before
public void doBeforeEachTestCase() {
    this.cache = new LRUCache<Integer, Integer>(2);
}

@Test
public void setTest() {
    this.cache.set(1, 1);
    assertThat(this.cache.size(), equalTo(1));
    assertThat(this.cache.get(1), equalTo(1));

    this.cache.set(2, 2);
    assertThat(this.cache.size(), equalTo(2));
    assertThat(this.cache.get(2), equalTo(2));

    this.cache.set(3, 3);
    assertThat(this.cache.size(), equalTo(2));
    assertThat(this.cache.get(3), equalTo(3));

    this.cache.set(1, 3);
    assertThat(this.cache.size(), equalTo(2));
    assertThat(this.cache.get(1), equalTo(3));

    assertThat(this.cache.get(4), equalTo(null));
}

}

0
craftsmannadeem