WeakHashMap et la mise en cache de Java: pourquoi renvoie-t-il les clés, pas les valeurs?
Java Weakhmap est souvent cité comme étant utile pour la mise en cache. Il semble étrange cependant que ses références faibles soient définies en termes de clés de la carte, pas ses valeurs. Je veux dire, c'est les valeurs que je veux mettre en cache, et que je veux que les ordures soient ramassées une fois que personne d'autre à part la cache Les renvoie fortement, non?
de quelle manière est-il utile de conserver de faibles références aux touches? Si vous faites un ExpensiveObject o = weakHashMap.get("some_key")
, alors je veux le cache tenez le " o "jusqu'à ce que l'appelant ne tienne plus la référence forte, et je ne me soucie pas du tout de l'objet de chaîne"some_key".
est-ce que je manque quelque chose?
4 réponses
WeakHashMap n'est pas utile en tant que cache, au moins la façon dont la plupart des gens pensent d'elle. Comme vous dites, il utilise faible clés , pas faible valeurs , donc il n'est pas conçu pour ce que la plupart des gens veulent l'utiliser pour (et, en fait, j'ai vu les gens l'utilisent pour, incorrectement).
WeakHashMap est surtout utile pour conserver des métadonnées sur des objets dont vous ne contrôlez pas le cycle de vie. Pour exemple, si vous avez un tas d'objets passant par votre classe, et vous voulez garder une trace de données supplémentaires à leur sujet sans avoir besoin d'être notifié quand ils sortent de portée, et sans votre référence à eux les gardant vivants.
un exemple simple (et un que j'ai déjà utilisé) pourrait être quelque chose comme:
WeakHashMap<Thread, SomeMetaData>
où vous pouvez garder une trace de ce que les différents threads dans votre système, lorsque le thread meurt, l'entrée sera supprimé silencieusement de votre carte, et vous n'empêcherez pas le fil d'être des ordures collectées si vous êtes la dernière référence à elle. Vous pouvez ensuite itérer les entrées de cette carte pour trouver les métadonnées que vous avez sur les threads actifs dans votre système.
See Weakhmap in not a cache! pour plus d'informations.
pour le type de cache que vous recherchez, utilisez un système de cache dédié (par exemple EHCache ) ou regardez google-collections Cartographe classe ; quelque chose comme
new MapMaker().weakValues().makeMap();
va faire ce que vous êtes après, ou si vous voulez obtenir fantaisie, vous pouvez ajouter expiration chronométrée:
new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();
l'utilisation principale de WeakHashMap
est quand vous avez des correspondances que vous voulez disparaître quand leurs clés disparaissent. Un cache est l'inverse---vous avez des mappings qui vous voulez à disparaître lorsque leurs valeurs disparaissent.
pour une cache, ce que vous voulez est un Map<K,SoftReference<V>>
. Un SoftReference
sera collectionné lorsque la mémoire se resserre. (Ceci contraste avec un WeakReference
, qui peut être effacé dès qu'il n'est plus dur référence à son référent.) Vous voulez que vos références soient douces dans un cache (au moins dans un cache où les correspondances des valeurs clés ne sont pas périmées), car il y a une chance que vos valeurs soient encore dans le cache si vous les cherchez plus tard. Si les références étaient faibles, vos valeurs seraient immédiatement gc'D, ce qui irait à l'encontre du but de la mise en cache.
pour plus de commodité, vous pourriez vouloir cacher les valeurs SoftReference
dans votre implémentation Map
, de sorte que votre cache semble être du type <K,V>
au lieu de <K,SoftReference<V>>
. Si vous voulez le faire, cette question a des propositions pour les implémentations disponibles sur le net.
Notez aussi que lorsque vous utilisez les valeurs SoftReference
dans un Map
, vous devez faire quelque chose pour enlever manuellement les paires de valeurs clés qui ont eu leur SoftReferences
effacé--sinon votre Map
ne fera que croître en taille pour toujours, et la mémoire fuit.
une autre chose à considérer est que si vous prenez l'approche Map<K, WeakReference<V>>
, la valeur peut disparaître, mais la cartographie ne le fera pas. Selon l'utilisation, vous pouvez donc vous retrouver avec une carte contenant de nombreuses entrées dont les références faibles ont été GC'D.
vous avez besoin de deux cartes: l'une qui correspond entre la clé de cache et les valeurs référencées faibles et l'autre dans la direction opposée à la cartographie entre les valeurs référencées faibles et les clés. Et vous avez besoin d'un queue de référence et un fil de nettoyage.
les références faibles ont la capacité de déplacer la référence dans une file d'attente lorsque l'objet référencé n'est plus accessible. Cette file d'attente doit être drainée par un fil de nettoyage. Et pour le nettoyage, il est nécessaire d'obtenir la clé pour une référence. C'est la raison pour laquelle la deuxième carte est nécessaire.
l'exemple suivant montre comment créer un cache avec une carte de hachage de références faibles. Lorsque vous exécutez le programme, vous obtenez le résultat suivant:
$ javac -Xlint:unchecked Cache.java && java Cache {even: [2, 4, 6], odd: [1, 3, 5]} {even: [2, 4, 6]}
la première ligne montre le contenu du cache avant que la référence à la liste impaire ait été supprimée et la deuxième ligne après les cotes ont été supprimé.
C'est le code:
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Cache<K,V>
{
ReferenceQueue<V> queue = null;
Map<K,WeakReference<V>> values = null;
Map<WeakReference<V>,K> keys = null;
Thread cleanup = null;
Cache ()
{
queue = new ReferenceQueue<V>();
keys = Collections.synchronizedMap (new HashMap<WeakReference<V>,K>());
values = Collections.synchronizedMap (new HashMap<K,WeakReference<V>>());
cleanup = new Thread() {
public void run() {
try {
for (;;) {
@SuppressWarnings("unchecked")
WeakReference<V> ref = (WeakReference<V>)queue.remove();
K key = keys.get(ref);
keys.remove(ref);
values.remove(key);
}
}
catch (InterruptedException e) {}
}
};
cleanup.setDaemon (true);
cleanup.start();
}
void stop () {
cleanup.interrupt();
}
V get (K key) {
return values.get(key).get();
}
void put (K key, V value) {
WeakReference<V> ref = new WeakReference<V>(value, queue);
keys.put (ref, key);
values.put (key, ref);
}
public String toString() {
StringBuilder str = new StringBuilder();
str.append ("{");
boolean first = true;
for (Map.Entry<K,WeakReference<V>> entry : values.entrySet()) {
if (first)
first = false;
else
str.append (", ");
str.append (entry.getKey());
str.append (": ");
str.append (entry.getValue().get());
}
str.append ("}");
return str.toString();
}
static void gc (int loop, int delay) throws Exception
{
for (int n = loop; n > 0; n--) {
Thread.sleep(delay);
System.gc(); // <- obstinate donkey
}
}
public static void main (String[] args) throws Exception
{
// Create the cache
Cache<String,List> c = new Cache<String,List>();
// Create some values
List odd = Arrays.asList(new Object[]{1,3,5});
List even = Arrays.asList(new Object[]{2,4,6});
// Save them in the cache
c.put ("odd", odd);
c.put ("even", even);
// Display the cache contents
System.out.println (c);
// Erase one value;
odd = null;
// Force garbage collection
gc (10, 10);
// Display the cache again
System.out.println (c);
// Stop cleanup thread
c.stop();
}
}