web-dev-qa-db-fra.com

Java WeakHashMap 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 cependant que ses références faibles soient définies en termes de clés de la carte, pas de ses valeurs. Je veux dire, ce sont les valeurs que je veux mettre en cache, et que je veux récupérer les ordures une fois que personne d'autre que le cache ne les référence fortement, non?

En quoi cela aide-t-il à conserver des références faibles aux touches? Si vous faites une ExpensiveObject o = weakHashMap.get("some_key"), alors je veux que le cache reste sur 'o' jusqu'à ce que l'appelant ne détienne plus la référence forte, et je ne me soucie pas du tout de l'objet chaîne "some_key" ".

Suis-je en train de manquer quelque chose?

63
Matthias

WeakHashMap n'est pas utile comme cache, du moins de la façon dont la plupart des gens y pensent. Comme vous le dites, il utilise des touches faibles , pas des valeurs faibles , donc il n'est pas conçu pour ce que la plupart des gens veulent l'utiliser (et, en fait, j'ai vu les gens l'utilisent incorrectement).

WeakHashMap est surtout utile pour conserver les métadonnées sur les objets dont vous ne contrôlez pas le cycle de vie. Par exemple, si vous avez un tas d'objets passant par votre classe, et que vous souhaitez garder une trace des données supplémentaires à leur sujet sans avoir besoin d'être averti quand ils sortent de la portée, et sans votre référence à leur maintien en vie.

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

WeakHashMap<Thread, SomeMetaData>

où vous pouvez garder une trace de ce que font les différents threads de votre système; lorsque le thread meurt, l'entrée sera supprimée silencieusement de votre carte, et vous n'empêcherez pas le thread d'être récupéré si vous en ê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 de 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 regardez google-collections 'classe MapMaker ; quelque chose comme

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

fera ce que vous recherchez, ou si vous voulez avoir de la fantaisie, vous pouvez ajouter une expiration programmée:

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

L'utilisation principale de WeakHashMap est lorsque vous avez des mappages que vous souhaitez supprimer lorsque leurs clés disparaissent. Un cache est l'inverse - vous avez des mappages que vous souhaitez supprimer lorsque leurs valeurs disparaissent.

Pour un cache, ce que vous voulez, c'est un Map<K,SoftReference<V>>. Un SoftReference sera récupéré lorsque la mémoire sera saturée. (Comparez cela avec un WeakReference, qui peut être effacé dès qu'il n'y a plus de référence matérielle à son référent.) Vous voulez que vos références soient douces dans un cache (au moins dans un où la valeur-clé les mappages ne deviennent pas périmés), 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 du but de la mise en cache.

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

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

34
uckelman

Une autre chose à considérer est que si vous prenez le Map<K, WeakReference<V>> approche, la valeur peut disparaître, mais pas le mappage. Selon l'utilisation, vous pouvez par conséquent vous retrouver avec une carte contenant de nombreuses entrées dont les références faibles ont été GC'd.

7
Shannon

Vous avez besoin de deux mappages: un mappage entre la clé de cache et les valeurs référencées faibles et un mappage dans le sens opposé entre les valeurs référencées faibles et les clés. Et vous avez besoin d'un file d'attente 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 file d'attente lorsque l'objet référencé n'est plus accessible. Cette file d'attente doit être vidé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 requise.

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ée 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 la suppression de la référence à la liste impaire et la deuxième ligne après la suppression des cotes.

Voici 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();
    }
}
6
ceving