Veuillez ne pas dire EHCache ou OSCache, etc. Supposons pour les besoins de cette question que je souhaite implémenter le mien en utilisant uniquement le SDK (apprentissage par l'action). Étant donné que le cache sera utilisé dans un environnement multithread, quelles structures de données utiliseriez-vous? J'en ai déjà implémenté un en utilisant LinkedHashMap et Collections # synchronizedMap , mais je suis curieux de savoir si l'une des nouvelles collections simultanées serait de meilleurs candidats.
MISE À JOUR: Je lisais juste le dernier de Yegge quand j'ai trouvé cette pépite:
Si vous avez besoin d'un accès à temps constant et que vous souhaitez maintenir l'ordre d'insertion, vous ne pouvez pas faire mieux qu'un LinkedHashMap, une structure de données vraiment merveilleuse. La seule façon dont cela pourrait être plus merveilleux est s'il y avait une version simultanée. Mais hélas.
Je pensais presque exactement la même chose avant de partir avec le LinkedHashMap
+Collections#synchronizedMap
implémentation j'ai mentionnée ci-dessus. Ravi de savoir que je n'avais pas simplement oublié quelque chose.
Sur la base des réponses jusqu'à présent, il semble que mon meilleur pari pour un LRU hautement concurrentiel serait d'étendre ConcurrentHashMap en utilisant une partie de la même logique que celle utilisée LinkedHashMap
.
la source
O(1)
version requise: stackoverflow.com/questions/23772102/…Réponses:
J'aime beaucoup de ces suggestions, mais pour l'instant je pense que je vais m'en tenir à
LinkedHashMap
+Collections.synchronizedMap
. Si je revisite à l'avenir, je vais probablement travailler sur l' extensionConcurrentHashMap
de la même manièreLinkedHashMap
s'étendHashMap
.METTRE À JOUR:
Sur demande, voici l'essentiel de ma mise en œuvre actuelle.
la source
LinkedHashMap
approuvent explicitement cette méthode pour créer une implémentation LRU.Si je faisais cela à partir de zéro aujourd'hui, j'utiliserais Guava
CacheBuilder
.la source
C'est le deuxième tour.
Le premier tour était ce que j'ai proposé puis j'ai relu les commentaires avec le domaine un peu plus enraciné dans ma tête.
Voici donc la version la plus simple avec un test unitaire qui montre qu'elle fonctionne sur la base d'autres versions.
D'abord la version non simultanée:
Le vrai drapeau suivra l'accès des get et des put. Voir JavaDocs. Le removeEdelstEntry sans l'indicateur true pour le constructeur implémenterait simplement un cache FIFO (voir les notes ci-dessous sur FIFO et removeEldestEntry).
Voici le test qui prouve qu'il fonctionne comme un cache LRU:
Maintenant pour la version simultanée ...
package org.boon.cache;
Vous pouvez voir pourquoi je couvre d'abord la version non simultanée. Les tentatives ci-dessus de créer des bandes pour réduire les conflits de verrouillage. Nous hachons donc la clé, puis recherchons ce hachage pour trouver le cache réel. Cela rend la taille limite plus une suggestion / estimation approximative avec une bonne quantité d'erreur en fonction de la répartition de votre algorithme de hachage de clés.
Voici le test pour montrer que la version simultanée fonctionne probablement. :) (Tester sous le feu serait le vrai moyen).
Ceci est le dernier message .. Le premier message que j'ai supprimé car c'était un LFU pas un cache LRU.
J'ai pensé que je donnerais une autre chance. J'essayais de trouver la version la plus simple d'un cache LRU en utilisant le JDK standard sans trop d'implémentation.
Voici ce que j'ai trouvé. Ma première tentative a été un peu un désastre car j'ai implémenté un LFU au lieu de et LRU, puis j'ai ajouté le support FIFO et LRU ... et puis j'ai réalisé que cela devenait un monstre. Puis j'ai commencé à parler à mon copain John qui était à peine intéressé, puis j'ai décrit en détail comment j'avais implémenté un LFU, un LRU et un FIFO et comment vous pouviez le changer avec un simple argument ENUM, puis j'ai réalisé que tout ce que je voulais vraiment était un simple LRU. Alors ignorez le message précédent de moi et faites-moi savoir si vous voulez voir un cache LRU / LFU / FIFO qui est commutable via une énumération ... non? Ok .. il y va.
Le LRU le plus simple possible en utilisant uniquement le JDK. J'ai implémenté à la fois une version simultanée et une version non simultanée.
J'ai créé une interface commune (c'est du minimalisme, il manque donc probablement quelques fonctionnalités que vous aimeriez mais cela fonctionne pour mes cas d'utilisation, mais laissez-moi savoir si vous souhaitez voir la fonctionnalité XYZ ... Je vis pour écrire du code.) .
Vous vous demandez peut-être ce que getSilent . J'utilise ceci pour tester. getSilent ne modifie pas le score LRU d'un élément.
D'abord le non-concurrent ...
La queue.removeFirstOccurrence est une opération potentiellement coûteuse si vous disposez d'un grand cache. On pourrait prendre LinkedList comme exemple et ajouter une carte de hachage de recherche inversée d'élément en nœud pour rendre les opérations de suppression BEAUCOUP PLUS RAPIDES et plus cohérentes. J'ai commencé aussi, mais j'ai réalisé que je n'en avais pas besoin. Mais peut-être...
Lorsque put est appelé, la clé est ajoutée à la file d'attente. Quand obtenir est appelé, la clé est supprimée et ajoutée à nouveau en haut de la file d'attente.
Si votre cache est petit et que la construction d'un objet coûte cher, cela devrait être un bon cache. Si votre cache est vraiment volumineux, la recherche linéaire pourrait être un goulot d'étranglement, surtout si vous n'avez pas de zones de cache chaudes. Plus les points chauds sont intenses, plus la recherche linéaire est rapide, car les éléments chauds sont toujours en haut de la recherche linéaire. Quoi qu'il en soit ... ce qui est nécessaire pour que cela aille plus vite, c'est d'écrire une autre LinkedList qui a une opération de suppression qui a une recherche inversée d'élément à nœud pour supprimer, puis la suppression serait à peu près aussi rapide que la suppression d'une clé d'une carte de hachage.
Si vous avez un cache de moins de 1000 éléments, cela devrait fonctionner correctement.
Voici un test simple pour montrer ses opérations en action.
Le dernier cache LRU était à thread unique, et veuillez ne pas l'envelopper dans un élément synchronisé ....
Voici un essai sur une version concurrente.
Les principales différences sont l'utilisation du ConcurrentHashMap au lieu de HashMap, et l'utilisation du Lock (j'aurais pu m'en tirer avec synchronisé, mais ...).
Je ne l'ai pas testé sous le feu, mais cela ressemble à un simple cache LRU qui pourrait fonctionner dans 80% des cas d'utilisation où vous avez besoin d'une simple carte LRU.
J'apprécie les commentaires, sauf pourquoi n'utilisez-vous pas la bibliothèque a, b ou c. La raison pour laquelle je n'utilise pas toujours une bibliothèque est que je ne veux pas toujours que chaque fichier de guerre fasse 80 Mo, et j'écris des bibliothèques, donc j'ai tendance à rendre les bibliothèques enfichables avec une solution assez bonne en place et quelqu'un peut brancher -dans un autre fournisseur de cache s'ils le souhaitent. :) Je ne sais jamais quand quelqu'un pourrait avoir besoin de Guava ou d'ehcache ou de quelque chose d'autre, je ne veux pas les inclure, mais si je rend la mise en cache plugable, je ne les exclurai pas non plus.
La réduction des dépendances a sa propre récompense. J'adore avoir des commentaires sur la façon de rendre cela encore plus simple ou plus rapide ou les deux.
Aussi, si quelqu'un connaît un prêt à partir ...
Ok .. Je sais ce que vous pensez ... Pourquoi n'utilise-t-il pas simplement removeEldest entrée de LinkedHashMap, et bien je devrais mais ... mais ... mais ... Ce serait un FIFO pas un LRU et nous étions essayer de mettre en œuvre un LRU.
Ce test échoue pour le code ci-dessus ...
Voici donc un cache FIFO rapide et sale utilisant removeEldestEntry.
Les FIFO sont rapides. Pas de recherche. Vous pourriez faire face à un FIFO devant un LRU et cela gérerait assez bien la plupart des entrées chaudes. Un meilleur LRU aura besoin de cet élément inversé en fonction de nœud.
Quoi qu'il en soit ... maintenant que j'ai écrit du code, laissez-moi parcourir les autres réponses et voir ce que j'ai manqué ... la première fois que je les ai scannées.
la source
LinkedHashMap
est O (1), mais nécessite une synchronisation. Pas besoin de réinventer la roue là-bas.2 options pour augmenter la concurrence:
1. Créez multiples
LinkedHashMap
et hachage en eux: par exemple:LinkedHashMap[4], index 0, 1, 2, 3
. Sur la touche fairekey%4
(oubinary OR
sur[key, 3]
) pour choisir quelle carte faire un put / get / remove.2. Vous pouvez faire un LRU «presque» en étendant
ConcurrentHashMap
et en ayant une carte de hachage liée comme une structure dans chacune des régions à l'intérieur. Le verrouillage se produirait de manière plus granulaire qu'unLinkedHashMap
qui est synchronisé. Sur unput
ouputIfAbsent
seulement un verrou sur la tête et la queue de la liste est nécessaire (par région). Lors d'un retrait ou d'une récupération, toute la région doit être verrouillée. Je suis curieux de savoir si des listes liées Atomic pourraient aider ici - probablement pour la tête de la liste. Peut-être pour plus.La structure ne conserverait pas l'ordre total, mais uniquement l'ordre par région. Tant que le nombre d'entrées est beaucoup plus grand que le nombre de régions, cela suffit pour la plupart des caches. Chaque région devra avoir son propre décompte d'entrées, celui-ci serait utilisé plutôt que le décompte global pour le déclencheur d'expulsion. Le nombre par défaut de régions dans a
ConcurrentHashMap
est de 16, ce qui est suffisant pour la plupart des serveurs aujourd'hui.serait plus facile à écrire et plus rapide avec une concurrence modérée.
serait plus difficile à écrire mais évoluerait beaucoup mieux avec une concurrence très élevée. Ce serait plus lent pour un accès normal (tout comme
ConcurrentHashMap
c'est plus lent queHashMap
là où il n'y a pas de concurrence)la source
Il existe deux implémentations open source.
Apache Solr a ConcurrentLRUCache: https://lucene.apache.org/solr/3_6_1/org/apache/solr/util/ConcurrentLRUCache.html
Il existe un projet open source pour un ConcurrentLinkedHashMap: http://code.google.com/p/concurrentlinkedhashmap/
la source
ConcurrentLinkedHashMap
est intéressante. Il prétend avoir été rouléMapMaker
depuis Guava, mais je ne l'ai pas repéré dans la documentation. Une idée de ce qui se passe avec cet effort?J'envisagerais d'utiliser J'envisagerais d' java.util.concurrent.PriorityBlockingQueue , avec une priorité déterminée par un compteur "numberOfUses" dans chaque élément. Je serais très, très prudent pour que toute ma synchronisation soit correcte, car le compteur "numberOfUses" implique que l'élément ne peut pas être immuable.
L'objet élément serait un wrapper pour les objets dans le cache:
la source
J'espère que cela t'aides .
la source
Le cache LRU peut être implémenté à l'aide d'un ConcurrentLinkedQueue et d'un ConcurrentHashMap qui peuvent également être utilisés dans un scénario multithreading. La tête de la file d'attente est l'élément qui se trouve dans la file d'attente le plus longtemps. La queue de la file d'attente est l'élément qui a été dans la file d'attente le plus court temps. Lorsqu'un élément existe dans la carte, nous pouvons le supprimer de LinkedQueue et l'insérer à la fin.
la source
put
.Voici mon implémentation pour LRU. J'ai utilisé PriorityQueue, qui fonctionne essentiellement comme FIFO et non threadsafe. Comparateur utilisé basé sur la création du temps de page et basé sur le exécute le classement des pages pour le temps le moins récemment utilisé.
Pages à considérer: 2, 1, 0, 2, 8, 2, 4
La page ajoutée au cache est: 2 La
page ajoutée au cache est: 1 La
page ajoutée au cache est: 0 La
page: 2 existe déjà dans le cache. Dernier accès mis à jour
Erreur de page, PAGE: 1, remplacée par PAGE: 8 La
page ajoutée au cache est: 8 La
page: 2 existe déjà dans le cache. Dernier accès mis à jour
Erreur de page, PAGE: 0, remplacée par PAGE: 4 La
page ajoutée au cache est: 4
PRODUCTION
LRUCache Pages
-------------
PageName: 8, PageCreationTime: 1365957019974
PageName: 2, PageCreationTime: 1365957020074
PageName: 4, PageCreationTime: 1365957020174
entrez le code ici
la source
Voici mon implémentation de cache LRU simultanée la plus performante testée sans aucun bloc synchronisé:
}
la source
C'est le cache LRU que j'utilise, qui encapsule un LinkedHashMap et gère la concurrence avec un simple verrou de synchronisation gardant les zones juteuses. Il "touche" les éléments au fur et à mesure qu'ils sont utilisés pour qu'ils redeviennent l'élément le plus "frais", de sorte qu'il s'agit en fait de LRU. J'avais également l'exigence que mes éléments aient une durée de vie minimale, que vous pouvez également considérer comme un "temps d'inactivité maximal" autorisé, alors vous êtes prêt pour l'expulsion.
Cependant, je suis d'accord avec la conclusion de Hank et la réponse acceptée - si je recommençais aujourd'hui, je vérifierais celle de Guava
CacheBuilder
.la source
Eh bien, pour un cache, vous rechercherez généralement des données via un objet proxy, (une URL, une chaîne ...) donc au niveau de l'interface, vous voudrez une carte. mais pour expulser les choses, vous voulez une structure semblable à une file d'attente. En interne, je maintiendrais deux structures de données, une file d'attente prioritaire et un HashMap. Voici une implémentation qui devrait pouvoir tout faire en temps O (1).
Voici un cours que j'ai organisé assez rapidement:
Voici comment ça fonctionne. Les clés sont stockées dans une liste chaînée avec les clés les plus anciennes au début de la liste (les nouvelles clés vont à l'arrière), donc lorsque vous devez `` éjecter '' quelque chose, il vous suffit de le sortir de l'avant de la file d'attente, puis d'utiliser la clé pour supprimer la valeur de la carte. Lorsqu'un élément est référencé, vous récupérez le ValueHolder de la carte, puis utilisez la variable queueelocation pour supprimer la clé de son emplacement actuel dans la file d'attente, puis placez-la à l'arrière de la file d'attente (c'est maintenant le plus récemment utilisé). Ajouter des choses est à peu près la même chose.
Je suis sûr qu'il y a une tonne d'erreurs ici et je n'ai implémenté aucune synchronisation. mais cette classe fournira O (1) l'ajout au cache, O (1) la suppression des anciens éléments et O (1) la récupération des éléments du cache. Même une synchronisation triviale (synchroniser simplement chaque méthode publique) aurait encore peu de conflits de verrouillage en raison de la durée d'exécution. Si quelqu'un a des astuces de synchronisation intelligentes, je serais très intéressé. En outre, je suis sûr qu'il existe des optimisations supplémentaires que vous pouvez implémenter à l'aide de la variable maxsize par rapport à la carte.
la source
LinkedHashMap
+Collections.synchronizedMap()
?Jetez un œil à ConcurrentSkipListMap . Cela devrait vous donner du temps à log (n) pour tester et supprimer un élément s'il est déjà contenu dans le cache, et un temps constant pour le rajouter.
Vous auriez juste besoin d'un compteur, etc. et d'un élément wrapper pour forcer l'ordre de l'ordre LRU et vous assurer que les éléments récents sont supprimés lorsque le cache est plein.
la source
ConcurrentSkipListMap
-il un avantage de facilité de mise en œuvreConcurrentHashMap
ou s'agit-il simplement d'éviter les cas pathologiques?ConcurrentSkipListMap
implémentation, je créerais une nouvelle implémentation de l'Map
interface qui délègueConcurrentSkipListMap
et effectue une sorte de wrapping afin que les types de clés arbitraires soient enveloppés dans un type qui est facilement trié en fonction du dernier accès?Voici ma courte mise en œuvre, veuillez la critiquer ou l'améliorer!
la source
Voici ma propre implémentation à ce problème
simplelrucache fournit une mise en cache LRU sûre pour les threads, très simple et non distribuée avec prise en charge TTL. Il fournit deux implémentations:
Vous pouvez le trouver ici: http://code.google.com/p/simplelrucache/
la source
Le meilleur moyen d'y parvenir est d'utiliser un LinkedHashMap qui maintient l'ordre d'insertion des éléments. Voici un exemple de code:
}
la source
Je recherche un meilleur cache LRU utilisant du code Java. Est-il possible pour vous de partager votre code de cache Java LRU en utilisant
LinkedHashMap
etCollections#synchronizedMap
? Actuellement, j'utiliseLRUMap implements Map
et le code fonctionne bien, mais je suis en trainArrayIndexOutofBoundException
de tester la charge avec 500 utilisateurs sur la méthode ci-dessous. La méthode déplace l'objet récent au début de la file d'attente.get(Object key)
andput(Object key, Object value)
method appelle lamoveToFront
méthode ci-dessus .la source
Je voulais ajouter un commentaire à la réponse donnée par Hank, mais je ne suis pas en mesure de le faire - veuillez le traiter comme un commentaire
LinkedHashMap maintient également l'ordre d'accès en fonction du paramètre passé dans son constructeur.Il conserve une liste doublement lignée pour maintenir l'ordre (voir LinkedHashMap.Entry)
@Pacerier il est correct que LinkedHashMap garde le même ordre pendant l'itération si l'élément est ajouté à nouveau mais ce n'est qu'en cas de mode ordre d'insertion.
c'est ce que j'ai trouvé dans la documentation java de l'objet LinkedHashMap.Entry
cette méthode prend soin de déplacer l'élément récemment accédé à la fin de la liste. Donc, dans l'ensemble, LinkedHashMap est la meilleure structure de données pour implémenter LRUCache.
la source
Une autre pensée et même une implémentation simple en utilisant la collection LinkedHashMap de Java.
LinkedHashMap a fourni la méthode removeEldestEntry et qui peut être remplacée de la manière mentionnée dans l'exemple. Par défaut, l'implémentation de cette structure de collection est fausse. Si son vrai et la taille de cette structure dépasse la capacité initiale, les éléments les plus anciens ou les plus anciens seront supprimés.
Nous pouvons avoir un pageno et un contenu de page dans mon cas pageno est un entier et pagecontent j'ai gardé la chaîne de valeurs de numéro de page.
Le résultat de l'exécution du code ci-dessus est le suivant:
la source
Suivant le concept @sanjanab (mais après correction), j'ai fait ma version du LRUCache en fournissant également le consommateur qui permet de faire quelque chose avec les éléments supprimés si nécessaire.
la source
Android propose une implémentation d'un cache LRU . le code est clair et simple.
la source