Java / caché basado en el tiempo con claves que caducan

¿Alguno de ustedes conoce un Java Map o un almacén de datos estándar similar que purga automáticamente las entradas después de un tiempo de espera determinado? Esto significa envejecimiento, donde las antiguas entradas caducadas “envejecen” automáticamente.

¿Preferiblemente en una biblioteca de código abierto a la que se puede acceder a través de Maven?

Conozco formas de implementar la funcionalidad yo mismo y lo he hecho varias veces en el pasado, por lo que no estoy pidiendo consejos al respecto, sino como indicadores de una buena implementación de referencia.

Las soluciones basadas en WeakReference como WeakHashMap no son una opción, porque es probable que mis claves sean cadenas no internas y quiero un tiempo de espera configurable que no dependa del recolector de elementos no utilizados.

Ehcache también es una opción en la que no me gustaría confiar porque necesita archivos de configuración externos. Estoy buscando una solución de solo código.

Sí. Google Collections, o Guava como se llama ahora, tiene algo llamado MapMaker que puede hacer exactamente eso.

ConcurrentMap graphs = new MapMaker() .concurrencyLevel(4) .softKeys() .weakValues() .maximumSize(10000) .expiration(10, TimeUnit.MINUTES) .makeComputingMap( new Function() { public Graph apply(Key key) { return createExpensiveGraph(key); } }); 

Actualizar:

A partir de guava 10.0 (publicado el 28 de septiembre de 2011) muchos de estos métodos de MapMaker han quedado en desuso en favor del nuevo CacheBuilder :

 LoadingCache graphs = CacheBuilder.newBuilder() .maximumSize(10000) .expireAfterWrite(10, TimeUnit.MINUTES) .build( new CacheLoader() { public Graph load(Key key) throws AnyException { return createExpensiveGraph(key); } }); 

Esta es una implementación de muestra que hice para el mismo requisito y la concurrencia funciona bien. Puede ser útil para alguien.

 import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * * @author Vivekananthan M * * @param  * @param  */ public class WeakConcurrentHashMap extends ConcurrentHashMap { private static final long serialVersionUID = 1L; private Map timeMap = new ConcurrentHashMap(); private long expiryInMillis = 1000; private static final SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss:SSS"); public WeakConcurrentHashMap() { initialize(); } public WeakConcurrentHashMap(long expiryInMillis) { this.expiryInMillis = expiryInMillis; initialize(); } void initialize() { new CleanerThread().start(); } @Override public V put(K key, V value) { Date date = new Date(); timeMap.put(key, date.getTime()); System.out.println("Inserting : " + sdf.format(date) + " : " + key + " : " + value); V returnVal = super.put(key, value); return returnVal; } @Override public void putAll(Map m) { for (K key : m.keySet()) { put(key, m.get(key)); } } @Override public V putIfAbsent(K key, V value) { if (!containsKey(key)) return put(key, value); else return get(key); } class CleanerThread extends Thread { @Override public void run() { System.out.println("Initiating Cleaner Thread.."); while (true) { cleanMap(); try { Thread.sleep(expiryInMillis / 2); } catch (InterruptedException e) { e.printStackTrace(); } } } private void cleanMap() { long currentTime = new Date().getTime(); for (K key : timeMap.keySet()) { if (currentTime > (timeMap.get(key) + expiryInMillis)) { V value = remove(key); timeMap.remove(key); System.out.println("Removing : " + sdf.format(new Date()) + " : " + key + " : " + value); } } } } } 

¡¡Aclamaciones!!

Puedes probar mi implementación de un mapa hash que expira. Esta implementación no utiliza subprocesos para eliminar entradas caducadas, sino que utiliza DelayQueue que se limpia en cada operación automáticamente.

Apache Commons tiene decorador para Map caducar: PassiveExpiringMap Es más simple que los cachés de Guava.

PD, ten cuidado, no está sincronizado.

Las colecciones de Google (guava) tienen el MapMaker en el que puede establecer un límite de tiempo (para la caducidad) y puede usar referencias suaves o débiles según lo elija utilizando un método de fábrica para crear instancias de su elección.

Parece que ehcache es excesivo para lo que quieras, pero ten en cuenta que no necesita archivos de configuración externos.

En general, es una buena idea mover la configuración a un archivo de configuración declarativa (para que no tenga que volver a comstackr cuando una nueva instalación requiera un tiempo de caducidad diferente), pero no es necesario en absoluto, aún así puede configurarlo mediante progtwigción. http://www.ehcache.org/documentation/user-guide/configuration

El caché de guayaba es fácil de implementar. Podemos caducar la clave en la base de tiempo usando guava caché. He leído completamente la publicación y a continuación le doy la clave de mi estudio.

 cache = CacheBuilder.newBuilder().refreshAfterWrite(2,TimeUnit.SECONDS). build(new CacheLoader(){ @Override public String load(String arg0) throws Exception { // TODO Auto-generated method stub return addcache(arg0); } } 

Referencia: ejemplo de caché de guayaba

puedes probar Expiring Map http://www.java2s.com/Code/Java/Collections-Data-Structure/ExpiringMap.htm una clase del Proyecto Apache MINA

Por lo general, una memoria caché debe mantener los objetos alrededor de un tiempo y debe exponerlos algún tiempo después. Lo que es un buen momento para sostener un objeto depende del caso de uso. Quería que esto fuera simple, sin hilos o progtwigdores. Este enfoque funciona para mí. A diferencia de SoftReference , se garantiza que los objetos estarán disponibles durante un tiempo mínimo. Sin embargo, no permanezcas en la memoria hasta que el sol se convierta en un gigante rojo .

Como ejemplo de uso, piense en un sistema que responda lentamente que podrá verificar si una solicitud se ha realizado recientemente, y en ese caso no realizar la acción solicitada dos veces, incluso si un usuario agitado pulsa el botón varias veces. Pero, si la misma acción se solicita algún tiempo después, se realizará nuevamente.

 class Cache { long avg, count, created, max, min; Map map = new HashMap(); /** * @param min minimal time [ns] to hold an object * @param max maximal time [ns] to hold an object */ Cache(long min, long max) { created = System.nanoTime(); this.min = min; this.max = max; avg = (min + max) / 2; } boolean add(T e) { boolean result = map.put(e, Long.valueOf(System.nanoTime())) != null; onAccess(); return result; } boolean contains(Object o) { boolean result = map.containsKey(o); onAccess(); return result; } private void onAccess() { count++; long now = System.nanoTime(); for (Iterator> it = map.entrySet().iterator(); it.hasNext();) { long t = it.next().getValue(); if (now > t + min && (now > t + max || now + (now - created) / count > t + avg)) { it.remove(); } } } } 

Si alguien necesita algo simple, el siguiente es un conjunto simple de caducidad de la llave. Puede convertirse fácilmente en un mapa.

 public class CacheSet { public static final int TIME_OUT = 86400 * 1000; LinkedHashMap linkedHashMap = new LinkedHashMap() { @Override protected boolean removeEldestEntry(Map.Entry eldest) { final long time = System.currentTimeMillis(); if( time - eldest.getValue().time > TIME_OUT) { Iterator i = values().iterator(); i.next(); do { i.remove(); } while( i.hasNext() && time - i.next().time > TIME_OUT ); } return false; } }; public boolean putIfNotExists(K key) { Hit value = linkedHashMap.get(key); if( value != null ) { return false; } linkedHashMap.put(key, new Hit()); return true; } private static class Hit { final long time; Hit() { this.time = System.currentTimeMillis(); } } }