Bitte sagen Sie nicht EHCache oder OSCache usw. Nehmen Sie für die Zwecke dieser Frage an, dass ich meine eigene nur mit dem SDK implementieren möchte (Learning by Doing). Welche Datenstrukturen würden Sie verwenden, da der Cache in einer Multithread-Umgebung verwendet wird? Ich habe bereits eine mit LinkedHashMap und Collections # synchronizedMap implementiert , bin aber gespannt, ob eine der neuen gleichzeitigen Sammlungen bessere Kandidaten wäre.
UPDATE: Ich habe gerade Yegges neueste Version gelesen, als ich dieses Nugget gefunden habe:
Wenn Sie einen zeitlich konstanten Zugriff benötigen und die Einfügereihenfolge beibehalten möchten, können Sie nichts Besseres tun als eine LinkedHashMap, eine wirklich wunderbare Datenstruktur. Der einzige Weg, wie es möglicherweise wundervoller sein könnte, wäre, wenn es eine gleichzeitige Version gäbe. Aber leider.
Ich habe fast genau das Gleiche gedacht, bevor ich mich für die oben erwähnte LinkedHashMap
+ Collections#synchronizedMap
Implementierung entschieden habe. Gut zu wissen, dass ich nicht einfach etwas übersehen hatte.
Basierend auf den bisherigen Antworten scheint es meine beste Wahl für eine hochkonkurrierende LRU zu sein, ConcurrentHashMap mit der gleichen Logik zu erweitern, die auch LinkedHashMap
verwendet wird.
quelle
O(1)
Erforderliche Version: stackoverflow.com/questions/23772102/…Antworten:
Ich mag viele dieser Vorschläge, aber ich denke, ich bleibe vorerst bei
LinkedHashMap
+Collections.synchronizedMap
. Wenn ich dies in Zukunft noch einmal besuche, werde ich wahrscheinlich daran arbeiten, aufConcurrentHashMap
die gleiche Weise zuLinkedHashMap
erweiternHashMap
.AKTUALISIEREN:
Auf Anfrage hier der Kern meiner aktuellen Implementierung.
quelle
LinkedHashMap
diese Methode zum Erstellen einer LRU-Implementierung explizit unterstützen.Wenn ich das heute noch einmal von Grund auf machen würde, würde ich Guavas verwenden
CacheBuilder
.quelle
Dies ist die zweite Runde.
Die erste Runde war das, was ich mir ausgedacht habe, dann habe ich die Kommentare mit der Domain noch einmal etwas tiefer in meinem Kopf verwurzelt gelesen.
Hier ist also die einfachste Version mit einem Komponententest, der zeigt, dass sie auf der Grundlage einiger anderer Versionen funktioniert.
Zuerst die nicht gleichzeitige Version:
Das True-Flag verfolgt den Zugriff auf Gets und Puts. Siehe JavaDocs. Der removeEdelstEntry ohne das true-Flag für den Konstruktor würde lediglich einen FIFO-Cache implementieren (siehe Hinweise unten zu FIFO und removeEldestEntry).
Hier ist der Test, der beweist, dass er als LRU-Cache funktioniert:
Nun zur gleichzeitigen Version ...
Paket org.boon.cache;
Sie können sehen, warum ich zuerst die nicht gleichzeitige Version behandele. Die obigen Versuche versuchen, einige Streifen zu erstellen, um Sperrenkonflikte zu reduzieren. Also hasht es den Schlüssel und sucht dann diesen Hash, um den tatsächlichen Cache zu finden. Dies macht die Grenzgröße eher zu einem Vorschlag / einer groben Vermutung innerhalb einer angemessenen Menge von Fehlern, abhängig davon, wie gut Ihr Schlüssel-Hash-Algorithmus verbreitet ist.
Hier ist der Test, um zu zeigen, dass die gleichzeitige Version wahrscheinlich funktioniert. :) (Test unter Beschuss wäre der echte Weg).
Dies ist der letzte Beitrag. Der erste Beitrag, den ich gelöscht habe, war eine LFU und kein LRU-Cache.
Ich dachte, ich würde es noch einmal versuchen. Ich habe versucht, die einfachste Version eines LRU-Caches mit dem Standard-JDK ohne zu viel Implementierung zu entwickeln.
Folgendes habe ich mir ausgedacht. Mein erster Versuch war ein bisschen katastrophal, als ich anstelle von und LRU eine LFU implementierte und dann FIFO und LRU-Unterstützung hinzufügte ... und dann merkte ich, dass es ein Monster wurde. Dann fing ich an, mit meinem Kumpel John zu sprechen, der kaum interessiert war, und dann beschrieb ich ausführlich, wie ich eine LFU, LRU und FIFO implementierte und wie man sie mit einem einfachen ENUM-Argument wechseln konnte, und dann wurde mir klar, dass alles, was ich wirklich wollte war eine einfache LRU. Ignorieren Sie also den früheren Beitrag von mir und lassen Sie mich wissen, ob Sie einen LRU / LFU / FIFO-Cache sehen möchten, der über eine Aufzählung umgeschaltet werden kann ... nein? Ok .. hier geht er.
Die einfachste mögliche LRU, die nur das JDK verwendet. Ich habe sowohl eine gleichzeitige als auch eine nicht gleichzeitige Version implementiert.
Ich habe eine gemeinsame Benutzeroberfläche erstellt (es ist ein Minimalismus, dem wahrscheinlich einige Funktionen fehlen, die Sie möchten, aber er funktioniert für meine Anwendungsfälle. Wenn Sie jedoch die Funktion XYZ sehen möchten, lassen Sie es mich wissen ... Ich lebe, um Code zu schreiben.) .
Sie fragen sich vielleicht, was getSilent ist. Ich benutze dies zum Testen. getSilent ändert die LRU-Punktzahl eines Elements nicht.
Zuerst die nicht gleichzeitige ....
Die queue.removeFirstOccurrence ist eine möglicherweise teure Operation, wenn Sie einen großen Cache haben. Man könnte LinkedList als Beispiel nehmen und eine Reverse-Lookup-Hash-Map von Element zu Knoten hinzufügen, um Entfernungsoperationen VIEL SCHNELLER und konsistenter zu machen. Ich habe auch angefangen, aber dann wurde mir klar, dass ich es nicht brauche. Aber vielleicht...
Wenn put aufgerufen wird, wird der Schlüssel zur Warteschlange hinzugefügt. Wenn get aufgerufen wird, wird der Schlüssel entfernt und oben in der Warteschlange wieder hinzugefügt.
Wenn Ihr Cache klein ist und das Erstellen eines Elements teuer ist, sollte dies ein guter Cache sein. Wenn Ihr Cache wirklich groß ist, kann die lineare Suche ein Flaschenhals sein, insbesondere wenn Sie keine heißen Cache-Bereiche haben. Je intensiver die Hot Spots sind, desto schneller ist die lineare Suche, da Hot Items immer ganz oben in der linearen Suche stehen. Wie auch immer ... Damit dies schneller geht, muss eine weitere LinkedList geschrieben werden, die über eine Entfernungsoperation verfügt, bei der ein umgekehrtes Element zur Knotensuche zum Entfernen verwendet wird. Das Entfernen ist dann ungefähr so schnell wie das Entfernen eines Schlüssels aus einer Hash-Map.
Wenn Sie einen Cache unter 1.000 Elementen haben, sollte dies gut funktionieren.
Hier ist ein einfacher Test, um seine Operationen in Aktion zu zeigen.
Der letzte LRU-Cache war Single-Threaded, und bitte verpacken Sie ihn nicht in etwas Synchronisiertes.
Hier ist ein Stich in eine gleichzeitige Version.
Die Hauptunterschiede sind die Verwendung der ConcurrentHashMap anstelle von HashMap und die Verwendung der Sperre (ich hätte mit synchronisiert davonkommen können, aber ...).
Ich habe es nicht unter Beschuss getestet, aber es scheint ein einfacher LRU-Cache zu sein, der in 80% der Anwendungsfälle funktioniert, in denen Sie eine einfache LRU-Karte benötigen.
Ich freue mich über Feedback, außer warum Sie die Bibliothek a, b oder c nicht verwenden. Der Grund, warum ich nicht immer eine Bibliothek verwende, ist, dass ich nicht immer möchte, dass jede War-Datei 80 MB groß ist, und ich schreibe Bibliotheken, sodass ich die Bibliotheken mit einer ausreichend guten Lösung steckbar mache und jemand einstecken kann -in einem anderen Cache-Anbieter, wenn sie möchten. :) Ich weiß nie, wann jemand Guava oder ehcache oder etwas anderes benötigt, das ich nicht einschließen möchte, aber wenn ich das Caching steckbar mache, werde ich sie auch nicht ausschließen.
Die Reduzierung von Abhängigkeiten hat ihre eigene Belohnung. Ich freue mich über Feedback, wie ich dies noch einfacher oder schneller oder beides machen kann.
Auch wenn jemand von einem Ready to Go weiß ....
Ok .. ich weiß was du denkst ... Warum benutzt er nicht einfach den Eintrag removeEldest von LinkedHashMap und nun sollte ich aber ... aber ... aber ... das wäre ein FIFO, kein LRU und wir waren es versuchen, eine LRU zu implementieren.
Dieser Test schlägt für den obigen Code fehl ...
Hier ist also ein schneller und schmutziger FIFO-Cache mit removeEldestEntry.
FIFOs sind schnell. Keine Suche. Sie könnten ein FIFO vor eine LRU stellen, und das würde die meisten heißen Einträge recht gut handhaben. Eine bessere LRU benötigt dieses Reverse-Element zur Node-Funktion.
Wie auch immer ... jetzt, wo ich Code geschrieben habe, lass mich die anderen Antworten durchgehen und sehen, was ich verpasst habe ... als ich sie das erste Mal gescannt habe.
quelle
LinkedHashMap
ist O (1), erfordert jedoch eine Synchronisation. Dort muss das Rad nicht neu erfunden werden.2 Optionen zur Erhöhung der Parallelität:
1. Erstellen Sie mehrere
LinkedHashMap
und hashen Sie sie ein: Beispiel :LinkedHashMap[4], index 0, 1, 2, 3
. Wählen Sie auf der Taste dokey%4
(oderbinary OR
on[key, 3]
) aus, welche Karte zum Put / Get / Remove verwendet werden soll.2. Sie können eine "fast" LRU erstellen
ConcurrentHashMap
, indem Sie eine verknüpfte Hash-Map-ähnliche Struktur in jeder der darin enthaltenen Regionen erstellen. Das Sperren würde granularer erfolgen als dasLinkedHashMap
, das synchronisiert wird. Bei einemput
oderputIfAbsent
nur einem Schloss am Kopf und am Ende der Liste wird benötigt (pro Region). Beim Entfernen oder Abrufen muss die gesamte Region gesperrt werden. Ich bin gespannt, ob Atomic-verknüpfte Listen hier helfen könnten - wahrscheinlich auch für den Kopf der Liste. Vielleicht für mehr.Die Struktur würde nicht die Gesamtreihenfolge beibehalten, sondern nur die Reihenfolge pro Region. Solange die Anzahl der Einträge viel größer ist als die Anzahl der Regionen, ist dies für die meisten Caches ausreichend. Jede Region muss eine eigene Anzahl von Einträgen haben. Diese wird anstelle der globalen Anzahl für den Räumungsauslöser verwendet. Die Standardanzahl von Regionen in a
ConcurrentHashMap
ist 16, was für die meisten Server heutzutage ausreichend ist.wäre einfacher zu schreiben und schneller bei mäßiger Parallelität.
Es wäre schwieriger zu schreiben, aber bei sehr hoher Parallelität viel besser zu skalieren. Es wäre langsamer für den normalen Zugriff (genauso wie
ConcurrentHashMap
es langsamer ist als dort,HashMap
wo es keine Parallelität gibt)quelle
Es gibt zwei Open Source-Implementierungen.
Apache Solr hat ConcurrentLRUCache: https://lucene.apache.org/solr/3_6_1/org/apache/solr/util/ConcurrentLRUCache.html
Es gibt ein Open Source-Projekt für eine ConcurrentLinkedHashMap: http://code.google.com/p/concurrentlinkedhashmap/
quelle
ConcurrentLinkedHashMap
ist interessant. Es wird behauptet,MapMaker
von Guava übernommen worden zu sein, aber ich habe es in den Dokumenten nicht entdeckt. Irgendeine Idee, was mit dieser Anstrengung los ist?Ich würde in Betracht ziehen, java.util.concurrent.PriorityBlockingQueue zu verwenden , wobei die Priorität durch einen "numberOfUses" -Zähler in jedem Element bestimmt wird. Ich würde sehr, sehr vorsichtig sein , um alle meine Synchronisationen korrekt zu machen, da der Zähler "numberOfUses" impliziert, dass das Element nicht unveränderlich sein kann.
Das Elementobjekt wäre ein Wrapper für die Objekte im Cache:
quelle
Hoffe das hilft .
quelle
Der LRU-Cache kann mithilfe einer ConcurrentLinkedQueue und einer ConcurrentHashMap implementiert werden, die auch im Multithreading-Szenario verwendet werden können. Der Kopf der Warteschlange ist das Element, das sich am längsten in der Warteschlange befunden hat. Das Ende der Warteschlange ist das Element, das sich in kürzester Zeit in der Warteschlange befunden hat. Wenn ein Element in der Map vorhanden ist, können wir es aus der LinkedQueue entfernen und am Ende einfügen.
quelle
put
.Hier ist meine Implementierung für LRU. Ich habe PriorityQueue verwendet, das im Grunde genommen als FIFO und nicht threadsicher funktioniert. Verwendeter Komparator basierend auf der Erstellung der Seitenzeit und basierend auf der Ausführung der Reihenfolge der Seiten für die zuletzt verwendete Zeit.
Zu berücksichtigende Seiten: 2, 1, 0, 2, 8, 2, 4
In den Cache hinzugefügte Seite ist: 2
In den Cache hinzugefügte Seite ist: 1
In den Cache hinzugefügte Seite ist: 0
Seite: 2 bereits im Cache vorhanden. Zuletzt aktualisierte Zugriffszeit Seitenfehler
, SEITE: 1, ersetzt durch SEITE: 8 Die
in den Cache hinzugefügte Seite ist: 8
Seite: 2 ist bereits im Cache vorhanden. Zuletzt aktualisierte
Zugriffszeit Seitenfehler, SEITE: 0, Ersetzt durch SEITE: 4 Die
in den Cache hinzugefügte Seite ist: 4
AUSGABE
LRUCache-Seiten
-------------
Seitenname: 8, PageCreationTime: 1365957019974
PageName: 2, PageCreationTime: 1365957020074
PageName: 4, PageCreationTime: 1365957020174
Code hier eingeben
quelle
Hier ist meine getestete leistungsstärkste gleichzeitige LRU-Cache-Implementierung ohne synchronisierten Block:
}}
quelle
Dies ist der von mir verwendete LRU-Cache, der eine LinkedHashMap kapselt und die Parallelität mit einer einfachen Synchronisierungssperre behandelt, die die saftigen Stellen schützt. Es "berührt" Elemente, wenn sie verwendet werden, so dass sie wieder das "frischeste" Element werden, so dass es tatsächlich LRU ist. Ich hatte auch die Anforderung, dass meine Elemente eine minimale Lebensdauer haben, die Sie auch als "maximal zulässige Leerlaufzeit" betrachten können, dann sind Sie zur Räumung bereit.
Ich stimme jedoch Hanks Schlussfolgerung zu und akzeptiere die Antwort - wenn ich heute wieder damit anfangen würde, würde ich mir Guavas ansehen
CacheBuilder
.quelle
Nun, für einen Cache suchen Sie im Allgemeinen einige Daten über ein Proxy-Objekt (eine URL, eine Zeichenfolge ...), sodass Sie in Bezug auf die Benutzeroberfläche eine Karte benötigen. Aber um die Dinge rauszuwerfen, möchten Sie eine Warteschlangenstruktur. Intern würde ich zwei Datenstrukturen pflegen, eine Priority-Queue und eine HashMap. Hier ist eine Implementierung, die in der Lage sein sollte, alles in O (1) Zeit zu erledigen.
Hier ist eine Klasse, die ich ziemlich schnell zusammengestellt habe:
So funktioniert das. Schlüssel werden in einer verknüpften Liste mit den ältesten Schlüsseln vorne in der Liste gespeichert (neue Schlüssel gehen nach hinten). Wenn Sie also etwas "auswerfen" müssen, legen Sie es einfach von der Vorderseite der Warteschlange ab und verwenden Sie dann den Schlüssel, um Entfernen Sie den Wert aus der Karte. Wenn auf ein Element verwiesen wird, nehmen Sie den ValueHolder aus der Karte und verwenden dann die Variable queuelocation, um den Schlüssel von seiner aktuellen Position in der Warteschlange zu entfernen und ihn dann am Ende der Warteschlange abzulegen (es ist jetzt die zuletzt verwendete). Das Hinzufügen von Dingen ist ziemlich gleich.
Ich bin mir sicher, dass es hier eine Menge Fehler gibt und ich habe keine Synchronisation implementiert. Diese Klasse bietet jedoch O (1) Hinzufügen zum Cache, O (1) Entfernen alter Elemente und O (1) Abrufen von Cache-Elementen. Selbst eine triviale Synchronisation (nur jede öffentliche Methode synchronisieren) hätte aufgrund der Laufzeit immer noch wenig Sperrenkonflikte. Wenn jemand clevere Synchronisationstricks hat, wäre ich sehr interessiert. Ich bin mir auch sicher, dass es einige zusätzliche Optimierungen gibt, die Sie mithilfe der Variablen maxsize in Bezug auf die Karte implementieren können.
quelle
LinkedHashMap
+Collections.synchronizedMap()
Implementierung implementiert werden?Schauen Sie sich ConcurrentSkipListMap an . Es sollte Ihnen log (n) Zeit zum Testen und Entfernen eines Elements geben, wenn es bereits im Cache enthalten ist, und konstante Zeit zum erneuten Hinzufügen.
Sie benötigen lediglich einen Zähler usw. und ein Wrapper-Element, um die Reihenfolge der LRU-Reihenfolge zu erzwingen und sicherzustellen, dass aktuelle Inhalte verworfen werden, wenn der Cache voll ist.
quelle
ConcurrentSkipListMap
eine einfache Implementierung Vorteile bringenConcurrentHashMap
, oder geht es einfach darum, pathologische Fälle zu vermeiden?ConcurrentSkipListMap
Implementierung würde ich also eine neue Implementierung derMap
Schnittstelle erstellen, die an eineConcurrentSkipListMap
Art Wrapping delegiert und diese ausführt, sodass die beliebigen Schlüsseltypen in einen Typ eingeschlossen werden, der leicht nach dem letzten Zugriff sortiert werden kann.Hier ist meine kurze Implementierung, bitte kritisieren oder verbessern Sie sie!
quelle
Hier ist meine eigene Implementierung zu diesem Problem
simplelrucache bietet threadsicheres, sehr einfaches, nicht verteiltes LRU-Caching mit TTL-Unterstützung. Es bietet zwei Implementierungen:
Sie finden es hier: http://code.google.com/p/simplelrucache/
quelle
Der beste Weg, dies zu erreichen, ist die Verwendung einer LinkedHashMap, die die Einfügereihenfolge der Elemente beibehält. Es folgt ein Beispielcode:
}}
quelle
Ich suche nach einem besseren LRU-Cache mit Java-Code. Können Sie Ihren Java LRU-Cache-Code mit
LinkedHashMap
und freigebenCollections#synchronizedMap
? Derzeit verwende ichLRUMap implements Map
und der Code funktioniert einwandfrei, aber ich macheArrayIndexOutofBoundException
mit der folgenden Methode Lasttests mit 500 Benutzern. Die Methode verschiebt das zuletzt verwendete Objekt vor die Warteschlange.get(Object key)
undput(Object key, Object value)
Methode ruft die obigemoveToFront
Methode auf.quelle
Wollte der Antwort von Hank einen Kommentar hinzufügen, aber einige, wie ich nicht in der Lage bin - bitte behandeln Sie es als Kommentar
LinkedHashMap verwaltet die Zugriffsreihenfolge auch basierend auf den in seinem Konstruktor übergebenen Parametern. Es wird eine doppelt gezeichnete Liste geführt, um die Reihenfolge aufrechtzuerhalten (siehe LinkedHashMap.Entry).
@Pacerier Es ist richtig, dass LinkedHashMap während der Iteration dieselbe Reihenfolge beibehält, wenn das Element erneut hinzugefügt wird, dies gilt jedoch nur für den Einfügungsreihenfolgenmodus.
Dies ist, was ich in Java-Dokumenten des LinkedHashMap.Entry-Objekts gefunden habe
Diese Methode sorgt dafür, dass das Element, auf das kürzlich zugegriffen wurde, an das Ende der Liste verschoben wird. Alles in allem ist LinkedHashMap also die beste Datenstruktur für die Implementierung von LRUCache.
quelle
Ein weiterer Gedanke und sogar eine einfache Implementierung mit der LinkedHashMap-Sammlung von Java.
LinkedHashMap hat die Methode removeEldestEntry bereitgestellt, die auf die im Beispiel erwähnte Weise überschrieben werden kann. Standardmäßig ist die Implementierung dieser Sammlungsstruktur falsch. Wenn die wahre Größe dieser Struktur über die ursprüngliche Kapazität hinausgeht, werden die ältesten oder älteren Elemente entfernt.
Wir können ein Pageno und einen Seiteninhalt haben. In meinem Fall ist Pageno eine Ganzzahl und ein Seiteninhalt. Ich habe die Zeichenfolge für die Seitenzahlwerte beibehalten.
Das Ergebnis der obigen Codeausführung ist wie folgt:
quelle
Nach dem @ sanjanab-Konzept (aber nach Korrekturen) habe ich meine Version des LRUCache erstellt, die auch den Consumer bereitstellt, der es ermöglicht, bei Bedarf etwas mit den entfernten Elementen zu tun.
quelle
Android bietet eine Implementierung eines LRU-Cache . Der Code ist sauber und unkompliziert.
quelle