WeakHashMap de Java et mise en cache: Pourquoi fait-il référence aux clés, pas aux valeurs?

WeakHashMap de Java est souvent cité comme étant utile pour la mise en cache. Il semble étrange que ses références faibles soient définies en termes de clés de la carte, et non de ses valeurs. Je veux dire, ce sont les valeurs que je veux mettre en cache et que je veux récupérer une fois que personne d’autre que le cache ne les référence, non?

De quelle manière cela aide-t-il de garder de faibles références aux clés? Si vous faites un ExpensiveObject o = weakHashMap.get("some_key") , alors je veux que le cache ExpensiveObject o = weakHashMap.get("some_key") o” jusqu’à ce que l’appelant ne détienne plus la référence forte, et je me moque du tout object de chaîne “some_key”.

Est-ce que je manque quelque chose?

WeakHashMap n’est pas utile en tant que cache, du moins comme le pensent la plupart des gens. Comme vous le dites, il utilise des clés faibles, et non des valeurs faibles, donc il n’est pas conçu pour ce que la plupart des gens veulent utiliser (et, en fait, j’ai vu des gens l’utiliser à tort).

WeakHashMap est surtout utile pour conserver les métadonnées sur les objects dont vous ne contrôlez pas le cycle de vie. Par exemple, si vous avez un tas d’objects transitant par votre classe et que vous souhaitez conserver des données supplémentaires à leur sujet sans avoir besoin d’être avertis lorsqu’ils sont hors de leur scope et sans que vous ne leur fassiez référence.

Un exemple simple (et un exemple que j’ai utilisé auparavant) pourrait être quelque chose comme:

 WeakHashMap 

où vous pouvez garder une trace de ce que font les différents threads de votre système; à la mort du thread, l’entrée sera supprimée silencieusement de votre map, et vous ne garderez pas le fil de discussion perdu si vous êtes la dernière référence. Vous pouvez ensuite parcourir les entrées de cette carte pour savoir quelles métadonnées vous avez sur les threads actifs dans votre système.

Voir WeakHashMap dans pas un 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 consultez la classe MapMaker de google-collections ; quelque chose comme

 new MapMaker().weakValues().makeMap(); 

fera ce que vous êtes après, ou si vous voulez obtenir de la fantaisie, vous pouvez append une expiration temporisée:

 new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap(); 

La principale utilisation de WeakHashMap est lorsque vous avez des mappages que vous souhaitez faire disparaître lorsque leurs clés disparaissent. Un cache est l’inverse – vous avez des mappages que vous voulez faire disparaître lorsque leurs valeurs disparaissent.

Pour un cache, vous voulez une Map> . Une SoftReference sera récupérée lorsque la mémoire sera serrée. (Comparez ceci avec un WeakReference , qui peut être effacé dès qu’il n’y a plus de référence à son référent.) Vous voulez que vos références soient souples dans un cache (du moins dans les cas où les mappages de valeurs-clés ne vont pas) stale), car il est possible que vos valeurs soient toujours dans le cache si vous les recherchez plus tard. Si les références étaient plutôt faibles, vos valeurs seraient immédiatement gc, ce qui irait à l’encontre de l’objective de la mise en cache.

Pour plus de commodité, vous souhaiterez peut-être masquer les valeurs de SoftReference dans votre implémentation Map , de sorte que votre cache semble être de type au lieu de > . Si vous voulez faire cela, cette question a des suggestions d’implémentations disponibles sur le net.

Notez également que lorsque vous utilisez des valeurs SoftReference dans une Map , vous devez faire quelque chose pour supprimer manuellement les paires clé-valeur dont les SoftReferences effacées — sinon votre Map ne fera que croître à jamais et perdra de la mémoire.

Une autre chose à considérer est que si vous prenez l’approche Map> , la valeur peut disparaître, mais pas le mappage. En fonction de l’utilisation, vous pouvez vous retrouver avec une carte contenant de nombreuses entrées dont les références faibles ont été atsortingbuées.

Vous avez besoin de deux cartes: une qui mappe entre la clé de cache et les valeurs faiblement référencées et une dans le sens opposé entre les valeurs de référence faibles et les clés. Et vous avez besoin d’une queue de référence et d’un thread de nettoyage.

Les références faibles ont la possibilité de déplacer la référence dans une queue lorsque l’object référencé n’est plus accessible. Cette queue doit être drainée par un thread 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 requirejse.

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 la sortie suivante:

 $ javac -Xlint: décoché Cache.java && java Cache
 {pair: [2, 4, 6], impair: [1, 3, 5]}
 {pair: [2, 4, 6]}

La première ligne affiche le contenu du cache avant que la référence à la liste impaire ait été supprimée et la deuxième ligne après la suppression des cotes.

Ceci 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 { ReferenceQueue queue = null; Map> values = null; Map,K> keys = null; Thread cleanup = null; Cache () { queue = new ReferenceQueue(); keys = Collections.synchronizedMap (new HashMap,K>()); values = Collections.synchronizedMap (new HashMap>()); cleanup = new Thread() { public void run() { try { for (;;) { @SuppressWarnings("unchecked") WeakReference ref = (WeakReference)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 ref = new WeakReference(value, queue); keys.put (ref, key); values.put (key, ref); } public Ssortingng toSsortingng() { SsortingngBuilder str = new SsortingngBuilder(); str.append ("{"); boolean first = true; for (Map.Entry> 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.toSsortingng(); } 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 c = new Cache(); // 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(); } }