java - HashMap con valores débiles
android weak-references (7)
Estoy implementando un caché para objetos almacenados de forma persistente. La idea es:
- Método
getObjectFromPersistence(long id); ///Takes about 3 seconds
getObjectFromPersistence(long id); ///Takes about 3 seconds
- Método
getObjectFromCache(long id) //Instantly
Y tenga un método: getObject(long id)
con el siguiente pseudocódigo:
synchronized(this){
CustomObject result= getObjectFromCache(id)
if (result==null){
result=getObjectFromPersistence(id);
addToCache(result);
}
return result;
}
Pero necesito permitir que el recolector de basura recoja el CustomObject. Hasta ahora estaba usando un HashMap<Long,WeakReference<CustomObject>
para la implementación. El problema es que con el tiempo el HashMap se llena de WeakReferences
vacías.
He comprobado WeakHashMap pero las claves son débiles (y los valores siguen siendo referencias sólidas), por lo que tener los largos con WeakReferences no tiene sentido.
¿Cuál es la mejor solución para resolver este problema? ¿Hay algún "WeakHashMap inverso" o algo similar?
Gracias
¿Has probado android.util.LruCache
(es una clase SDK11 pero también está en el paquete de compatibilidad como android.support.v4.util.LruCache
). No implementa java.util.Map
pero funciona como un Mapa y puede definir cuánta memoria tomará y vaciará los objetos antiguos (los objetos almacenados en caché no utilizados por sí mismos).
Creo que la mejor opción (si una dependencia de Guava no es deseable) sería usar una subclase personalizada de WeakReference que recuerde su ID, de modo que su hilo de limpieza pueda eliminar los valores débiles durante la limpieza de las WeakReferences.
La implementación de la referencia débil, con el correspondiente ReferenceQueue y el hilo de limpieza se vería algo así:
class CustomObjectAccess {
private static final ReferenceQueue<CustomObject> releasedCustomObjects =
new ReferenceQueue<>();
static {
Thread cleanupThread = new Thread("CustomObject cleanup thread")
while (true) {
CustomObjectWeakReference freed = (CustomObjectWeakReference)
CustomObjectWeakReference.releasedCustomObjects.remove();
cache.remove(freed.id);
}
};
cleanupThread.start();
}
private Map<CustomObjectID, CustomObjectWeakReference> cache;
public CustomObject get(CustomObjectID id) {
synchronized(this){
CustomObject result= getFromCache(id);
if (result==null) {
result=getObjectFromPersistence(id);
addToCache(result);
}
}
return result;
}
private addToCache(CustomObject co) {
cache.put(CustomObject.getID(), new CustomObjectWeakReference(co));
}
private getFromCache(CustomObjectID id) {
WeakReference<CustomObject> weak = cache.get(id);
if (weak != null) {
return weak.get();
}
return null;
}
class CustomObjectWeakReference extends WeakReference<CustomObject> {
private final CustomObjectID id;
CustomObjectWeakReference(CustomObject co) {
super(co, releasedCustomObjects);
this.id = co.getID();
}
}
}
Podría comenzar una "limpieza": enhebrar de vez en cuando. Tal vez si el tamaño de su mapa supera un umbral pero a lo sumo cada 5 minutos ... algo así.
Mantenga los ciclos de limpieza cortos para no bloquear la funcionalidad principal.
Puedes usar Guava MapMaker
para esto:
ConcurrentMap<Long, CustomObject> graphs = new MapMaker()
.weakValues()
.makeMap();
Incluso puede incluir la parte de cálculo reemplazando makeMap()
con esto:
.makeComputingMap(
new Function<Long, CustomObject>() {
public CustomObject apply(Long id) {
return getObjectFromPersistence(id);
}
});
Dado que lo que está escribiendo se parece mucho a un caché, el caché más nuevo y especializado (creado a través de un CacheBuilder
) podría ser aún más relevante para usted. No implementa la interfaz del Map
directamente, pero proporciona aún más controles que puede desear para un caché.
Puede consultar this para obtener información detallada sobre cómo trabajar con CacheBuilder y aquí hay un ejemplo para un acceso rápido:
LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Integer, String>() {
@Override
public String load(Integer id) throws Exception {
return "value";
}
}
);
Se WeakReference
una WeakReference
a su ReferenceQueue
suministrada en el momento de la construcción cuando se recopila su referencia.
Puede poll
el ReferenceQueue
cada vez que acceda al caché y mantener un HashMap<WeakReference<CustomObject>,Long>
para saber qué entrada eliminar si se encuentra una referencia en la cola.
Alternativamente, si el caché no se usa con frecuencia, puede ver la cola en un hilo separado.
También puede probar WeakValueHashMap desde jboss-common http://docs.jboss.org/jbossas/javadoc/4.0.2/org/jboss/util/collection/WeakValueHashMap.java.html
Tenía la necesidad de almacenar objetos débiles etiquetados y en lugar de usar WeakHashMap<String, T>
, podía usar WeakHashMap<T, String>
lugar.
Esto es Kotlin, pero debería aplicarse a Java por igual:
abstract class InstanceFactory<T> {
@Volatile
private var instances: MutableMap<T, String> = WeakHashMap<T, String>()
protected fun getOrCreate(tag: String = SINGLETON, creator: () -> T): T =
findByTag(tag)?.let {
it
} ?: synchronized(this) {
findByTag(tag)?.let {
it
} ?: run {
creator().also {
instances[it] = tag
}
}
}
private fun findByTag(tag: String): T? = instances.entries.find { it.value == tag }?.key
companion object {
const val SINGLETON = "singleton"
}
}
Esto se puede utilizar de la siguiente manera:
class Thing(private val dependency: Dep) { ... }
class ThingFactory(private val dependency: Dep) : InstanceFactory<Thing>() {
createInstance(tag: String): Thing = getOrCreate(tag) { Thing(dependency) }
}
Singletons simples se pueden hacer así:
object ThingFactory {
getInstance(dependency: Dependency): Thing = getOrCreate { Thing(dependency) }
}