70 votes

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

Java est WeakHashMap est souvent cité comme étant utile pour la mise en cache. Il semble étrange cependant que la faiblesse de ses références sont définis en fonction de la carte de touches, pas de ses valeurs. Je veux dire, c'est les valeurs que je veux cache, et que je veux obtenir des ordures collectées une fois que personne d'autre à part le cache est fortement leur référencement, non?

De quelle manière est-il utile de tenir des références faibles pour les clés? Si vous faites un ExpensiveObject o = weakHashMap.get("some_key"), alors je veux le cache de s'accrocher à " o " jusqu'à ce que l'appelant n'a pas la référence forte, et de plus, je ne m'inquiète pas au sujet de l'objet string "some_key".

Ai-je raté quelque chose?

121voto

Cowan Points 17235

WeakHashMap n'est pas utile en tant que cache, au moins la façon dont la plupart des gens pensent d'elle. Comme vous le dites, il utilise la faiblesse des touches, pas de faibles valeurs, de sorte qu'il n'est pas conçu pour ce que la plupart des gens veulent l'utiliser pour (et, en fait, j'ai vu des gens l'utilisent pour, à tort).

WeakHashMap est surtout utile pour garder des métadonnées sur des objets dont le cycle de vie vous n'avez pas de contrôle. Par exemple, si vous avez un tas d'objets en passant par votre classe, et que vous voulez garder une trace des données supplémentaires sur eux sans avoir besoin d'être avisé lorsqu'ils passent hors de portée, et sans référence à eux pour les maintenir en vie.

Un exemple simple (et que j'ai utilisé avant) 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 sont en train de faire; lorsque le thread meurt, l'entrée sera retiré en silence à partir de votre carte, et vous n'aurez pas à garder le Fil de déchets sont collectées que si vous êtes la dernière référence. Vous pouvez ensuite effectuer une itération sur les entrées dans la carte pour trouver ce que les métadonnées que vous avez au sujet de threads actifs dans votre système.

Voir WeakHashMap dans les pas d'un cache! pour plus d'informations.

Pour le type de cache que vous êtes après, soit utilisez un système de cache (par exemple, EHCache) ou de regarder google-collections" Cartographe classe; quelque chose comme

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

à faire ce que vous êtes après, ou si vous voulez obtenir la fantaisie, vous pouvez ajouter chronométré d'expiration:

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

39voto

uckelman Points 7228

L'utilisation principale de la WeakHashMap , c'est quand vous avez mappings qui vous voulez à disparaître lorsque leurs clés de disparaître. Un cache est l'inverse---vous avez des mappings qui vous voulez à disparaître lorsque leurs valeurs disparaissent.

Pour un cache, ce que vous voulez, c'est un Map<K,SoftReference<V>>. Un SoftReference sera le garbage collector, quand la mémoire devient rare. (Ceci contraste avec un WeakReference, qui peut être effacé dès qu'il n'est plus dur, en référence à son référent.) Vous voulez que vos références à être doux dans un cache (au moins dans un clé-valeur des mappages de ne pas aller rassis), car alors il ya une chance que vos valeurs seront toujours dans le cache si vous regardez pour eux plus tard. Si les références sont faibles au lieu de cela, vos valeurs de gc avais tout de suite, défaisant le but de la mise en cache.

Pour plus de commodité, vous pouvez masquer l' SoftReference valeurs à l'intérieur de votre Map mise en œuvre, 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 pour les implémentations disponibles sur le net.

7voto

rehevkor5 Points 382

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

7voto

ceving Points 3990

Vous avez besoin de deux cartes: l'une des cartes entre le cache de la clé et de la faiblesse des référencé valeurs et l'un dans la direction opposée de la cartographie entre la faible référencé valeurs et les touches. Et vous avez besoin d'une référence de la file d'attente et un thread de nettoyage.

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é ne peut pas accéder plus longtemps. Cette file d'attente doit être évacué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 nécessaire.

L'exemple suivant montre comment créer un cache avec un hachage de la carte de références faibles. Lorsque vous exécutez le programme, vous obtenez le résultat suivant:

$ javac -Xlint:décoché Cache.java && Cache de java
{même: [2, 4, 6], bizarre: [1, 3, 5]}
{même: [2, 4, 6]}

La première ligne affiche le contenu du cache avant de la référence à l'étrange liste a été supprimé et la deuxième ligne, après que les chances ont été supprimés.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X