web-dev-qa-db-fra.com

Java carte / cache temporels avec clés expirantes

L'un de vous connaît-il une carte Java ou un magasin de données standard similaire qui purge automatiquement les entrées après un délai donné? Cela signifie vieillissement, où les anciennes entrées expirées sont automatiquement supprimées.

De préférence dans une bibliothèque open source accessible via Maven?

Je connais des moyens de mettre en œuvre la fonctionnalité moi-même et je l’ai déjà fait plusieurs fois dans le passé. Je ne demande donc pas de conseils à cet égard, mais des indications pour une bonne implémentation de référence.

Les solutions basées sur WeakReference telles que WeakHashMap ne sont pas une option, car mes clés sont susceptibles d'être des chaînes non internées et je souhaite un délai d'expiration configurable ne dépendant pas du garbage collector.

Ehcache est également une option sur laquelle je ne voudrais pas compter car il a besoin de fichiers de configuration externes. Je cherche une solution en code seulement.

232
Sean Patrick Floyd

Oui. Google Collections, ou Guava , comme son nom l'indique maintenant, s'appelle MapMaker et peut faire exactement cela.

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

Mise à jour:

Depuis la version 10.0 de Guava (publiée le 28 septembre 2011), plusieurs de ces méthodes MapMaker sont obsolètes en faveur de la nouvelle version 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);
          }
        });
301
Shervin Asgari

Voici un exemple d'implémentation que j'ai réalisé pour le même besoin et la simultanéité fonctionne bien. Pourrait être utile pour quelqu'un.

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


Lien Git Repo (avec implémentation d'auditeur)

https://github.com/vivekjustthink/WeakConcurrentHashMap

À votre santé!!

23
Vivek

Apache Commons a un décorateur pour que la carte expire les entrées: PassiveExpiringMap C'est plus simple que les caches de Guava.

P.S. attention, ce n'est pas synchronisé.

17
Guram Savinov

Vous pouvez essayer ma mise en œuvre d'une carte de hachage à expiration automatique. Cette implémentation n'utilise pas de threads pour supprimer les entrées expirées, mais utilise DelayQueue qui est automatiquement nettoyée à chaque opération.

17
pcan

Les collections Google (goyave) ont le MapMaker dans lequel vous pouvez définir une limite de temps (pour l'expiration) et vous pouvez utiliser une référence souple ou faible selon votre choix en utilisant une méthode d'usine pour créer les instances de votre choix.

3
Emil

On dirait que ehcache est excessif pour ce que vous voulez, mais notez qu'il n'a pas besoin de fichiers de configuration externes.

Il est généralement judicieux de déplacer la configuration dans des fichiers de configuration déclaratifs (vous n'avez donc pas besoin de recompiler lorsqu'une nouvelle installation nécessite un délai d'expiration différent), mais ce n'est pas du tout nécessaire, vous pouvez toujours le configurer par programme. http://www.ehcache.org/documentation/user-guide/configuration

3
dan carter

Le cache de goyave est facile à mettre en œuvre. Nous pouvons expirer la clé sur la base de temps en utilisant le cache de goyave. J'ai lu entièrement le post et donne ci-dessous la clé de mon étude.

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

              }

Référence: exemple de cache guava

2
Anuj Dhiman

Si quelqu'un a besoin d'une chose simple, voici un ensemble de clés expirant. Il pourrait être facilement converti en carte.

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

vous pouvez essayer Expiring Map http://www.Java2s.com/Code/Java/Collections-Data-Structure/ExpiringMap.htm une classe du projet Apache MINA

2
toby941

En règle générale, une mémoire cache doit conserver les objets pendant un certain temps et en exposer un peu plus tard. Ce qui est un bon moment pour tenir un objet dépend du cas d'utilisation. Je voulais que cette chose soit simple, pas de threads ou de planificateurs. Cette approche fonctionne pour moi. Contrairement à SoftReferences, la disponibilité des objets est garantie pendant un minimum de temps. Cependant, ne restez pas dans la mémoire jusqu'à ce que le soleil se transforme en une géante rouge .

Comme exemple d'utilisation, pensez à un système à réponse lente qui doit être en mesure de vérifier si une demande a été faite récemment et, dans ce cas, de ne pas exécuter l'action demandée deux fois, même si un utilisateur agité appuie plusieurs fois sur le bouton. Mais, si la même action est demandée quelque temps plus tard, elle doit être exécutée à nouveau.

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