WeakHashMap y almacenamiento en caché de Java: ¿por qué hace referencia a las claves, no a los valores?

StackOverflow https://stackoverflow.com/questions/1802809

  •  05-07-2019
  •  | 
  •  

Pregunta

Java WeakHashMap se cita a menudo como útil para el almacenamiento en caché. Sin embargo, parece extraño que sus referencias débiles se definan en términos de las claves del mapa, no de sus valores. Quiero decir, son los valores que quiero almacenar en caché, y los que quiero que se recolecte basura una vez que nadie más que el caché hace referencia a ellos, ¿no?

¿De qué manera ayuda mantener referencias débiles a las teclas? Si haces un ExpensiveObject o = weakHashMap.get("some_key"), entonces quiero que el caché se mantenga en 'o' hasta que la persona que llama ya no tenga la referencia fuerte, y no me importa en absoluto el objeto de cadena & Quot; some_key " ;.

¿Me estoy perdiendo algo?

¿Fue útil?

Solución

WeakHashMap no es útil como caché, al menos de la forma en que la mayoría de la gente lo piensa. Como usted dice, usa teclas débiles, no valores débiles, por lo que no está diseñado para lo que la mayoría de la gente quiere usar (y, de hecho, he < em> visto la gente lo usa incorrectamente).

WeakHashMap es principalmente útil para mantener metadatos sobre objetos cuyo ciclo de vida no controlas. Por ejemplo, si tiene un montón de objetos que pasan a través de su clase, y desea realizar un seguimiento de los datos adicionales sobre ellos sin necesidad de ser notificado cuando salgan del alcance, y sin su referencia a ellos para mantenerlos con vida.

Un ejemplo simple (y uno que he usado antes) podría ser algo como:

WeakHashMap<Thread, SomeMetaData>

donde puede realizar un seguimiento de lo que están haciendo varios subprocesos en su sistema; cuando el hilo muere, la entrada se eliminará silenciosamente de tu mapa, y no evitarás que el hilo se recoja si eres la última referencia a él. Luego puede iterar sobre las entradas en ese mapa para averiguar qué metadatos tiene sobre los hilos activos en su sistema.

Consulte ¡WeakHashMap en un caché! para más información.

Para el tipo de caché que está buscando, utilice un sistema de caché dedicado (por ejemplo, EHCache ) o mire google-collections ' clase MapMaker ; algo como

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

hará lo que buscas, o si quieres ponerte elegante, puedes agregar vencimiento programado:

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

Otros consejos

El uso principal de WeakHashMap es cuando tiene asignaciones que desea que desaparezcan cuando desaparezcan sus claves. Un caché es lo contrario: tiene asignaciones que desea que desaparezcan cuando desaparezcan sus valores.

Para un caché, lo que quieres es un Map<K,SoftReference<V>>. Un SoftReference será recogida de basura cuando la memoria se pone apretada (Compare esto con un WeakReference, que puede borrarse tan pronto como ya no haya una referencia sólida a su referente). Desea que sus referencias sean blandas en un caché (al menos en una donde las asignaciones de valores clave no " t rancio), desde entonces existe la posibilidad de que sus valores aún estén en la memoria caché si los busca más adelante. Si, en cambio, las referencias fueran débiles, sus valores se seleccionarían de inmediato, lo que anularía el propósito del almacenamiento en caché.

Por conveniencia, es posible que desee ocultar los valores Map dentro de su implementación <K,V>, de modo que su caché parezca ser del tipo <K,SoftReference<V>> en lugar de SoftReferences. Si desea hacer eso, esta pregunta tiene sugerencias para implementaciones disponibles en el neto.

Tenga en cuenta también que cuando utiliza los valores <=> en un <=>, debe hacer algo para eliminar manualmente los pares clave-valor que han tenido su <=> borrado --- de lo contrario, su <=> solo crecerá de tamaño para siempre y perderá memoria.

Otra cosa a considerar es que si adopta el enfoque Map<K, WeakReference<V>>, el valor puede desaparecer, pero la asignación no lo hará. Dependiendo del uso, como resultado puede terminar con un Mapa que contiene muchas entradas cuyas Referencias Débiles han sido GC'd.

Necesita dos mapas: uno que asigne entre la clave de caché y valores de referencia débil y uno en la asignación de dirección opuesta entre los valores de referencia débiles y las claves. Y necesita una cola de referencia y un hilo de limpieza.

Las referencias débiles tienen la capacidad de mover la referencia a una cola cuando ya no se puede acceder al objeto referenciado. Esta cola tiene que ser drenada por un hilo de limpieza. Y para la limpieza es necesario obtener la clave para una referencia. Esta es la razón por la cual se requiere el segundo mapa.

El siguiente ejemplo muestra cómo crear un caché con un mapa hash de referencias débiles. Cuando ejecuta el programa, obtiene el siguiente resultado:

$ javac -Xlint:unchecked Cache.java && java Cache
{even: [2, 4, 6], odd: [1, 3, 5]}
{even: [2, 4, 6]}

La primera línea muestra el contenido de la memoria caché antes de que se haya eliminado la referencia a la lista impar y la segunda línea después de que se hayan eliminado las probabilidades.

Este es el código:

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();
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top