example - manejo de cache en java
Mapa/caché basado en el tiempo de Java con claves caducadas (10)
¿Alguno de ustedes conoce un Mapa de Java o un almacén de datos estándar similar que purga las entradas después de un tiempo de espera determinado? Esto significa el envejecimiento, donde las entradas antiguas caducadas "envejecen" automáticamente.
Preferiblemente en una biblioteca de código abierto que es accesible a través de Maven?
Conozco formas de implementar la funcionalidad por mí mismo y lo he hecho varias veces en el pasado, por lo que no pido consejos al respecto, sino sugerencias para una buena implementación de referencia.
WeakReference soluciones basadas en WeakReference como WeakHashMap no son una opción, porque es probable que mis claves no sean cadenas internas y quiero un tiempo de espera configurable que no dependa del recolector de basura.
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 código solamente.
Apache Commons tiene un decorador para que el Mapa caduque: PassiveExpiringMap Es más simple que los cachés de Guava.
PD: cuidado, no está sincronizado.
El caché de guayaba es fácil de implementar. Podemos vencer la clave en la base de tiempo usando el caché de guayaba. He leído completamente el post y debajo doy la clave de mi estudio.
cache = CacheBuilder.newBuilder().refreshAfterWrite(2,TimeUnit.SECONDS).
build(new CacheLoader<String, String>(){
@Override
public String load(String arg0) throws Exception {
// TODO Auto-generated method stub
return addcache(arg0);
}
}
Referencia: ejemplo de caché de guayaba
Esta es una implementación de ejemplo que hice para el mismo requisito y la concurrencia funciona bien. Podría 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 <K>
* @param <V>
*/
public class WeakConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
private static final long serialVersionUID = 1L;
private Map<K, Long> timeMap = new ConcurrentHashMap<K, Long>();
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<? extends K, ? extends V> 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!!
Las colecciones de Google (guayaba) tienen el MapMaker en el que puede establecer el límite de tiempo (para el vencimiento) y puede usar referencias débiles o débiles según lo desee, utilizando un método de fábrica para crear instancias de su elección.
Normalmente, un caché debe mantener los objetos alrededor de algún tiempo y exponerlos un tiempo después. El momento adecuado para sujetar un objeto depende del caso de uso. Quería que esto fuera simple, sin hilos o programadores. Este enfoque funciona para mí. A diferencia de SoftReference
s, se garantiza que los objetos estarán disponibles durante un tiempo mínimo. Sin embargo, no permanezcan en la memoria hasta que el sol se convierta en una gigante roja .
Como ejemplo de uso, piense en un sistema de respuesta lenta que podrá comprobar 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á de nuevo.
class Cache<T> {
long avg, count, created, max, min;
Map<T, Long> map = new HashMap<T, Long>();
/**
* @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<Entry<T, Long>> 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();
}
}
}
}
Parece que ehcache es una exageración de lo que desea, sin embargo, tenga en cuenta que no necesita archivos de configuración externos.
Por lo general, es una buena idea mover la configuración a un archivo de configuración declarativo (por lo que no es necesario volver a compilar cuando una nueva instalación requiere un tiempo de caducidad diferente), pero no es necesario en absoluto, aún puede configurarlo mediante programación. http://www.ehcache.org/documentation/user-guide/configuration
Puedes probar mi implementación de un mapa hash autoexpansible. Esta implementación no hace uso de subprocesos para eliminar entradas caducadas, en su lugar utiliza DelayQueue que se limpia en cada operación automáticamente.
Sí. Google Collections, o Guava como se le llama ahora, tiene algo llamado MapMaker que puede hacer exactamente eso.
ConcurrentMap<Key, Graph> graphs = new MapMaker()
.concurrencyLevel(4)
.softKeys()
.weakValues()
.maximumSize(10000)
.expiration(10, TimeUnit.MINUTES)
.makeComputingMap(
new Function<Key, Graph>() {
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<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
Si alguien necesita algo simple, lo siguiente es un conjunto simple que expira la clave. Podría ser convertido en un mapa fácilmente.
public class CacheSet<K> {
public static final int TIME_OUT = 86400 * 1000;
LinkedHashMap<K, Hit> linkedHashMap = new LinkedHashMap<K, Hit>() {
@Override
protected boolean removeEldestEntry(Map.Entry<K, Hit> eldest) {
final long time = System.currentTimeMillis();
if( time - eldest.getValue().time > TIME_OUT) {
Iterator<Hit> 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();
}
}
}
puede probar Expiring Map http://www.java2s.com/Code/Java/Collections-Data-Structure/ExpiringMap.htm una clase de The Apache MINA Project