web-dev-qa-db-fra.com

Comment implémenteriez-vous un cache LRU en Java?

Veuillez ne pas dire EHCache ou OSCache, etc. Supposons que, pour les besoins de cette question, je veuille mettre en œuvre le mien en utilisant uniquement le SDK (apprendre par la pratique). Etant donné que le cache sera utilisé dans un environnement multithread, quelles infrastructures de données utiliseriez-vous? J'en ai déjà implémenté une avec LinkedHashMap et Collections # synchronizedMap , mais je suis curieux de savoir si l'une des nouvelles collections simultanées serait de meilleurs candidats.

UPDATE: Je lisais/ le dernier numéro de Yegge quand j'ai trouvé cette pépite:

Si vous avez besoin d'un accès constant et que vous souhaitez conserver l'ordre d'insertion, vous ne pouvez pas faire mieux qu'un LinkedHashMap, une structure de données vraiment merveilleuse. La seule façon de rendre cela plus merveilleux serait d’avoir une version concurrente. Mais hélas.

Je pensais à peu près exactement la même chose avant d’utiliser l’implémentation LinkedHashMap + Collections#synchronizedMap que j’ai mentionnée ci-dessus. C'est bien de savoir que je n'avais pas simplement oublié quelque chose.

D'après les réponses reçues jusqu'à présent, il semble que mon meilleur choix pour un LRU hautement concurrentiel serait d'étendre ConcurrentHashMap en utilisant une partie de la même logique que celle utilisée par LinkedHashMap.

160
Hank Gay

Si je recommençais aujourd'hui à partir de rien, je me servirais de CacheBuilder de Guava.

13
Hank Gay

J'aime beaucoup de ces suggestions, mais pour l'instant, je pense que je vais m'en tenir à LinkedHashMap + Collections.synchronizedMap. Si je revisite cela à l'avenir, je travaillerai probablement sur l'extension ConcurrentHashMap de la même manière que LinkedHashMap extend HashMap.

METTRE À JOUR:

Sur demande, voici l'essentiel de ma mise en œuvre actuelle.

private class LruCache<A, B> extends LinkedHashMap<A, B> {
    private final int maxEntries;

    public LruCache(final int maxEntries) {
        super(maxEntries + 1, 1.0f, true);
        this.maxEntries = maxEntries;
    }

    /**
     * Returns <tt>true</tt> if this <code>LruCache</code> has more entries than the maximum specified when it was
     * created.
     *
     * <p>
     * This method <em>does not</em> modify the underlying <code>Map</code>; it relies on the implementation of
     * <code>LinkedHashMap</code> to do that, but that behavior is documented in the JavaDoc for
     * <code>LinkedHashMap</code>.
     * </p>
     *
     * @param eldest
     *            the <code>Entry</code> in question; this implementation doesn't care what it is, since the
     *            implementation is only dependent on the size of the cache
     * @return <tt>true</tt> if the oldest
     * @see Java.util.LinkedHashMap#removeEldestEntry(Map.Entry)
     */
    @Override
    protected boolean removeEldestEntry(final Map.Entry<A, B> eldest) {
        return super.size() > maxEntries;
    }
}

Map<String, String> example = Collections.synchronizedMap(new LruCache<String, String>(CACHE_SIZE));
98
Hank Gay
18
Joe

C'est le deuxième tour. 

Le premier tour est ce que j’ai trouvé puis je relis les commentaires avec le domaine un peu plus enraciné dans ma tête. 

Donc, voici la version la plus simple avec un test unitaire qui montre que cela fonctionne sur d'autres versions.

Tout d'abord la version non concurrente:

import Java.util.LinkedHashMap;
import Java.util.Map;

public class LruSimpleCache<K, V> implements LruCache <K, V>{

    Map<K, V> map = new LinkedHashMap (  );


    public LruSimpleCache (final int limit) {
           map = new LinkedHashMap <K, V> (16, 0.75f, true) {
               @Override
               protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
                   return super.size() > limit;
               }
           };
    }
    @Override
    public void put ( K key, V value ) {
        map.put ( key, value );
    }

    @Override
    public V get ( K key ) {
        return map.get(key);
    }

    //For testing only
    @Override
    public V getSilent ( K key ) {
        V value =  map.get ( key );
        if (value!=null) {
            map.remove ( key );
            map.put(key, value);
        }
        return value;
    }

    @Override
    public void remove ( K key ) {
        map.remove ( key );
    }

    @Override
    public int size () {
        return map.size ();
    }

    public String toString() {
        return map.toString ();
    }


}

Le vrai drapeau suivra l'accès des entrées et des sorties. Voir JavaDocs. RemoveEdelstEntry sans l'indicateur true au constructeur implémenterait simplement un cache FIFO (voir les notes ci-dessous sur FIFO et removeEldestEntry).

Voici le test qui prouve qu'il fonctionne comme un cache LRU:

public class LruSimpleTest {

    @Test
    public void test () {
        LruCache <Integer, Integer> cache = new LruSimpleCache<> ( 4 );


        cache.put ( 0, 0 );
        cache.put ( 1, 1 );

        cache.put ( 2, 2 );
        cache.put ( 3, 3 );


        boolean ok = cache.size () == 4 || die ( "size" + cache.size () );


        cache.put ( 4, 4 );
        cache.put ( 5, 5 );
        ok |= cache.size () == 4 || die ( "size" + cache.size () );
        ok |= cache.getSilent ( 2 ) == 2 || die ();
        ok |= cache.getSilent ( 3 ) == 3 || die ();
        ok |= cache.getSilent ( 4 ) == 4 || die ();
        ok |= cache.getSilent ( 5 ) == 5 || die ();


        cache.get ( 2 );
        cache.get ( 3 );
        cache.put ( 6, 6 );
        cache.put ( 7, 7 );
        ok |= cache.size () == 4 || die ( "size" + cache.size () );
        ok |= cache.getSilent ( 2 ) == 2 || die ();
        ok |= cache.getSilent ( 3 ) == 3 || die ();
        ok |= cache.getSilent ( 4 ) == null || die ();
        ok |= cache.getSilent ( 5 ) == null || die ();


        if ( !ok ) die ();

    }

Maintenant pour la version concurrente ...

package org.boon.cache;

import Java.util.LinkedHashMap;
import Java.util.Map;
import Java.util.concurrent.locks.ReadWriteLock;
import Java.util.concurrent.locks.ReentrantReadWriteLock;

public class LruSimpleConcurrentCache<K, V> implements LruCache<K, V> {

    final CacheMap<K, V>[] cacheRegions;


    private static class CacheMap<K, V> extends LinkedHashMap<K, V> {
        private final ReadWriteLock readWriteLock;
        private final int limit;

        CacheMap ( final int limit, boolean fair ) {
            super ( 16, 0.75f, true );
            this.limit = limit;
            readWriteLock = new ReentrantReadWriteLock ( fair );

        }

        protected boolean removeEldestEntry ( final Map.Entry<K, V> eldest ) {
            return super.size () > limit;
        }


        @Override
        public V put ( K key, V value ) {
            readWriteLock.writeLock ().lock ();

            V old;
            try {

                old = super.put ( key, value );
            } finally {
                readWriteLock.writeLock ().unlock ();
            }
            return old;

        }


        @Override
        public V get ( Object key ) {
            readWriteLock.writeLock ().lock ();
            V value;

            try {

                value = super.get ( key );
            } finally {
                readWriteLock.writeLock ().unlock ();
            }
            return value;
        }

        @Override
        public V remove ( Object key ) {

            readWriteLock.writeLock ().lock ();
            V value;

            try {

                value = super.remove ( key );
            } finally {
                readWriteLock.writeLock ().unlock ();
            }
            return value;

        }

        public V getSilent ( K key ) {
            readWriteLock.writeLock ().lock ();

            V value;

            try {

                value = this.get ( key );
                if ( value != null ) {
                    this.remove ( key );
                    this.put ( key, value );
                }
            } finally {
                readWriteLock.writeLock ().unlock ();
            }
            return value;

        }

        public int size () {
            readWriteLock.readLock ().lock ();
            int size = -1;
            try {
                size = super.size ();
            } finally {
                readWriteLock.readLock ().unlock ();
            }
            return size;
        }

        public String toString () {
            readWriteLock.readLock ().lock ();
            String str;
            try {
                str = super.toString ();
            } finally {
                readWriteLock.readLock ().unlock ();
            }
            return str;
        }


    }

    public LruSimpleConcurrentCache ( final int limit, boolean fair ) {
        int cores = Runtime.getRuntime ().availableProcessors ();
        int stripeSize = cores < 2 ? 4 : cores * 2;
        cacheRegions = new CacheMap[ stripeSize ];
        for ( int index = 0; index < cacheRegions.length; index++ ) {
            cacheRegions[ index ] = new CacheMap<> ( limit / cacheRegions.length, fair );
        }
    }

    public LruSimpleConcurrentCache ( final int concurrency, final int limit, boolean fair ) {

        cacheRegions = new CacheMap[ concurrency ];
        for ( int index = 0; index < cacheRegions.length; index++ ) {
            cacheRegions[ index ] = new CacheMap<> ( limit / cacheRegions.length, fair );
        }
    }

    private int stripeIndex ( K key ) {
        int hashCode = key.hashCode () * 31;
        return hashCode % ( cacheRegions.length );
    }

    private CacheMap<K, V> map ( K key ) {
        return cacheRegions[ stripeIndex ( key ) ];
    }

    @Override
    public void put ( K key, V value ) {

        map ( key ).put ( key, value );
    }

    @Override
    public V get ( K key ) {
        return map ( key ).get ( key );
    }

    //For testing only
    @Override
    public V getSilent ( K key ) {
        return map ( key ).getSilent ( key );

    }

    @Override
    public void remove ( K key ) {
        map ( key ).remove ( key );
    }

    @Override
    public int size () {
        int size = 0;
        for ( CacheMap<K, V> cache : cacheRegions ) {
            size += cache.size ();
        }
        return size;
    }

    public String toString () {

        StringBuilder builder = new StringBuilder ();
        for ( CacheMap<K, V> cache : cacheRegions ) {
            builder.append ( cache.toString () ).append ( '\n' );
        }

        return builder.toString ();
    }


}

Vous pouvez voir pourquoi je couvre d'abord la version non concurrente. Ce qui précède tente de créer des bandes pour réduire les conflits de verrous. Donc, il hache la clé puis lève la hache pour trouver le cache réel. Cela fait de la taille limite une suggestion/approximation approximative avec une bonne quantité d’erreurs en fonction de la qualité de la propagation de l’algorithme de hachage de votre clé.

Voici le test pour montrer que la version concurrente fonctionne probablement. :) (Test sous le feu serait la vraie façon).

public class SimpleConcurrentLRUCache {


    @Test
    public void test () {
        LruCache <Integer, Integer> cache = new LruSimpleConcurrentCache<> ( 1, 4, false );


        cache.put ( 0, 0 );
        cache.put ( 1, 1 );

        cache.put ( 2, 2 );
        cache.put ( 3, 3 );


        boolean ok = cache.size () == 4 || die ( "size" + cache.size () );


        cache.put ( 4, 4 );
        cache.put ( 5, 5 );

        puts (cache);
        ok |= cache.size () == 4 || die ( "size" + cache.size () );
        ok |= cache.getSilent ( 2 ) == 2 || die ();
        ok |= cache.getSilent ( 3 ) == 3 || die ();
        ok |= cache.getSilent ( 4 ) == 4 || die ();
        ok |= cache.getSilent ( 5 ) == 5 || die ();


        cache.get ( 2 );
        cache.get ( 3 );
        cache.put ( 6, 6 );
        cache.put ( 7, 7 );
        ok |= cache.size () == 4 || die ( "size" + cache.size () );
        ok |= cache.getSilent ( 2 ) == 2 || die ();
        ok |= cache.getSilent ( 3 ) == 3 || die ();

        cache.put ( 8, 8 );
        cache.put ( 9, 9 );

        ok |= cache.getSilent ( 4 ) == null || die ();
        ok |= cache.getSilent ( 5 ) == null || die ();


        puts (cache);


        if ( !ok ) die ();

    }


    @Test
    public void test2 () {
        LruCache <Integer, Integer> cache = new LruSimpleConcurrentCache<> ( 400, false );


        cache.put ( 0, 0 );
        cache.put ( 1, 1 );

        cache.put ( 2, 2 );
        cache.put ( 3, 3 );


        for (int index =0 ; index < 5_000; index++) {
            cache.get(0);
            cache.get ( 1 );
            cache.put ( 2, index  );
            cache.put ( 3, index );
            cache.put(index, index);
        }

        boolean ok = cache.getSilent ( 0 ) == 0 || die ();
        ok |= cache.getSilent ( 1 ) == 1 || die ();
        ok |= cache.getSilent ( 2 ) != null || die ();
        ok |= cache.getSilent ( 3 ) != null || die ();

        ok |= cache.size () < 600 || die();
        if ( !ok ) die ();



    }

}

Ceci est le dernier message. Le premier message que j'ai supprimé car il s'agissait d'un cache LFU et non d'un cache LRU.

Je pensais que j'allais donner un autre coup. J'essayais de trouver la version la plus simple d'un cache LRU en utilisant le JDK standard sans trop d'implémentation. 

Voici ce que je suis venu avec. Ma première tentative a été un peu un désastre lorsque j'ai implémenté une LFU au lieu de et une LRU, puis j'ai ajouté la FIFO et le support de la LRU ... et puis j'ai réalisé que cela devenait un monstre. Ensuite, j'ai commencé à parler à mon ami John, qui était à peine intéressé, puis j'ai décrit en détail comment j'ai implémenté une LFU, une LRU et FIFO et comment vous pouvez le changer avec un simple argument ENUM. réalisé que tout ce que je voulais vraiment était un simple LRU. Donc ignorez le précédent message de moi et faites-moi savoir si vous voulez voir un cache LRU/LFU/FIFO qui est commutable via une enum ... non? Ok .. le voilà.

Le LRU le plus simple possible utilisant uniquement le JDK. J'ai mis en œuvre une version concurrente et une version non concurrente.

J'ai créé une interface commune (le minimalisme manque probablement quelques fonctionnalités que vous voudriez, mais cela fonctionne pour mes cas d'utilisation, mais si vous souhaitez voir la fonctionnalité XYZ, laissez-moi savoir ... je vis pour écrire du code.) .

public interface LruCache<KEY, VALUE> {
    void put ( KEY key, VALUE value );

    VALUE get ( KEY key );

    VALUE getSilent ( KEY key );

    void remove ( KEY key );

    int size ();
}

Vous pouvez vous demander ce quegetSilentest. Je l'utilise pour les tests. getSilent ne modifie pas le score LRU d'un élément. 

D'abord le non-concurrent ....

import Java.util.Deque;
import Java.util.HashMap;
import Java.util.LinkedList;
import Java.util.Map;

public class LruCacheNormal<KEY, VALUE> implements LruCache<KEY,VALUE> {

    Map<KEY, VALUE> map = new HashMap<> ();
    Deque<KEY> queue = new LinkedList<> ();
    final int limit;


    public LruCacheNormal ( int limit ) {
        this.limit = limit;
    }

    public void put ( KEY key, VALUE value ) {
        VALUE oldValue = map.put ( key, value );

        /*If there was already an object under this key,
         then remove it before adding to queue
         Frequently used keys will be at the top so the search could be fast.
         */
        if ( oldValue != null ) {
            queue.removeFirstOccurrence ( key );
        }
        queue.addFirst ( key );

        if ( map.size () > limit ) {
            final KEY removedKey = queue.removeLast ();
            map.remove ( removedKey );
        }

    }


    public VALUE get ( KEY key ) {

        /* Frequently used keys will be at the top so the search could be fast.*/
        queue.removeFirstOccurrence ( key );
        queue.addFirst ( key );
        return map.get ( key );
    }


    public VALUE getSilent ( KEY key ) {

        return map.get ( key );
    }

    public void remove ( KEY key ) {

        /* Frequently used keys will be at the top so the search could be fast.*/
        queue.removeFirstOccurrence ( key );
        map.remove ( key );
    }

    public int size () {
        return map.size ();
    }

    public String toString() {
        return map.toString ();
    }
}

Le queue.removeFirstOccurrence est une opération potentiellement coûteuse si vous avez un cache volumineux. On pourrait prendre LinkedList comme exemple et ajouter une carte de hachage de recherche inversée d'élément à noeud pour rendre les opérations de suppression beaucoup plus rapides et cohérentes. J'ai aussi commencé, mais j'ai réalisé que je n'en avais pas besoin. Mais peut-être...

Lorsqueputest appelé, la clé est ajoutée à la file d'attente. Lorsquegetest appelé, la clé est supprimée et ré-ajoutée en haut de la file d'attente. 

Si votre cache est petit et que la construction d'un objet est chère, alors cela devrait être un bon cache. Si votre cache est vraiment volumineux, la recherche linéaire pourrait être un goulot d'étranglement, surtout si vous n'avez pas de zones de cache chaudes. Plus les points chauds sont intenses, plus la recherche linéaire est rapide, car les éléments chauds figurent toujours en haut de la recherche linéaire. Quoi qu'il en soit ... ce qu'il faut pour que cela aille plus vite, c'est écrire une autre LinkedList qui a une opération de suppression qui a la recherche inverse pour supprimer le noeud, puis supprimer serait aussi rapide que de supprimer une clé d'une carte de hachage.

Si vous avez une mémoire cache inférieure à 1 000 éléments, cela devrait fonctionner correctement.

Voici un test simple pour montrer ses opérations en action. 

public class LruCacheTest {

    @Test
    public void test () {
        LruCache<Integer, Integer> cache = new LruCacheNormal<> ( 4 );


        cache.put ( 0, 0 );
        cache.put ( 1, 1 );

        cache.put ( 2, 2 );
        cache.put ( 3, 3 );


        boolean ok = cache.size () == 4 || die ( "size" + cache.size () );
        ok |= cache.getSilent ( 0 ) == 0 || die ();
        ok |= cache.getSilent ( 3 ) == 3 || die ();


        cache.put ( 4, 4 );
        cache.put ( 5, 5 );
        ok |= cache.size () == 4 || die ( "size" + cache.size () );
        ok |= cache.getSilent ( 0 ) == null || die ();
        ok |= cache.getSilent ( 1 ) == null || die ();
        ok |= cache.getSilent ( 2 ) == 2 || die ();
        ok |= cache.getSilent ( 3 ) == 3 || die ();
        ok |= cache.getSilent ( 4 ) == 4 || die ();
        ok |= cache.getSilent ( 5 ) == 5 || die ();

        if ( !ok ) die ();

    }
}

Le dernier cache LRU était mono-thread, et ne l'enroulez pas dans un objet synchronisé .... 

Voici un coup de poignard à une version concurrente.

import Java.util.Deque;
import Java.util.LinkedList;
import Java.util.Map;
import Java.util.concurrent.ConcurrentHashMap;
import Java.util.concurrent.locks.ReentrantLock;

public class ConcurrentLruCache<KEY, VALUE> implements LruCache<KEY,VALUE> {

    private final ReentrantLock lock = new ReentrantLock ();


    private final Map<KEY, VALUE> map = new ConcurrentHashMap<> ();
    private final Deque<KEY> queue = new LinkedList<> ();
    private final int limit;


    public ConcurrentLruCache ( int limit ) {
        this.limit = limit;
    }

    @Override
    public void put ( KEY key, VALUE value ) {
        VALUE oldValue = map.put ( key, value );
        if ( oldValue != null ) {
            removeThenAddKey ( key );
        } else {
            addKey ( key );
        }
        if (map.size () > limit) {
            map.remove ( removeLast() );
        }
    }


    @Override
    public VALUE get ( KEY key ) {
        removeThenAddKey ( key );
        return map.get ( key );
    }


    private void addKey(KEY key) {
        lock.lock ();
        try {
            queue.addFirst ( key );
        } finally {
            lock.unlock ();
        }


    }

    private KEY removeLast( ) {
        lock.lock ();
        try {
            final KEY removedKey = queue.removeLast ();
            return removedKey;
        } finally {
            lock.unlock ();
        }
    }

    private void removeThenAddKey(KEY key) {
        lock.lock ();
        try {
            queue.removeFirstOccurrence ( key );
            queue.addFirst ( key );
        } finally {
            lock.unlock ();
        }

    }

    private void removeFirstOccurrence(KEY key) {
        lock.lock ();
        try {
            queue.removeFirstOccurrence ( key );
        } finally {
            lock.unlock ();
        }

    }


    @Override
    public VALUE getSilent ( KEY key ) {
        return map.get ( key );
    }

    @Override
    public void remove ( KEY key ) {
        removeFirstOccurrence ( key );
        map.remove ( key );
    }

    @Override
    public int size () {
        return map.size ();
    }

    public String toString () {
        return map.toString ();
    }
}

Les principales différences sont l'utilisation de ConcurrentHashMap à la place de HashMap et l'utilisation de Lock (j'aurais pu m'en tirer avec synchronisé, mais ...). 

Je ne l'ai pas testé sous le feu, mais cela ressemble à un simple cache LRU qui pourrait fonctionner dans 80% des cas d'utilisation où vous avez besoin d'une simple carte LRU.

Les commentaires sont les bienvenus, sauf que vous n’utilisez pas la bibliothèque a, b ou c . Si je n’utilise pas toujours une bibliothèque, c’est que je ne veux pas toujours que tous les fichiers de guerre fassent 80 Mo, J'écris des bibliothèques donc j'ai tendance à rendre les bibliothèques plug -ables avec une solution assez bonne en place et que quelqu'un puisse brancher un autre fournisseur de cache s'il le souhaite. :) Je ne sais jamais quand une personne pourrait avoir besoin de Guava, d'e-cache ou de quelque chose d'autre, je ne veux pas les inclure, mais si je mets la mise en cache en plug -able, je ne les exclurai pas non plus. 

La réduction des dépendances a sa propre récompense. J'aime recevoir des commentaires sur la manière de rendre cela encore plus simple, plus rapide ou les deux.

Aussi, si quelqu'un sait d'un prêt à partir ....Ok .. Je sais ce que vous pensez ... Pourquoi n'utilise-t-il pas simplement l'entrée removeEldest de LinkedHashMap, et bien j'aurais dû mais .... mais .. mais .. mais ce serait un FIFO pas un LRU et nous essayions de mettre en œuvre un LRU.

Map<KEY, VALUE> map = new LinkedHashMap<KEY, VALUE> () { @Override protected boolean removeEldestEntry ( Map.Entry<KEY, VALUE> eldest ) { return this.size () > limit; } }; .

        cache.get ( 2 );
        cache.get ( 3 );
        cache.put ( 6, 6 );
        cache.put ( 7, 7 );
        ok |= cache.size () == 4 || die ( "size" + cache.size () );
        ok |= cache.getSilent ( 2 ) == 2 || die ();
        ok |= cache.getSilent ( 3 ) == 3 || die ();
        ok |= cache.getSilent ( 4 ) == null || die ();
        ok |= cache.getSilent ( 5 ) == null || die ();
.

import Java.util.*;

public class FifoCache<KEY, VALUE> implements LruCache<KEY,VALUE> {

    final int limit;

    Map<KEY, VALUE> map = new LinkedHashMap<KEY, VALUE> () {

        @Override
        protected boolean removeEldestEntry ( Map.Entry<KEY, VALUE> eldest ) {
            return this.size () > limit;
        }
    };


    public LruCacheNormal ( int limit ) {
        this.limit = limit;
    }

    public void put ( KEY key, VALUE value ) {
         map.put ( key, value );


    }


    public VALUE get ( KEY key ) {

        return map.get ( key );
    }


    public VALUE getSilent ( KEY key ) {

        return map.get ( key );
    }

    public void remove ( KEY key ) {
        map.remove ( key );
    }

    public int size () {
        return map.size ();
    }

    public String toString() {
        return map.toString ();
    }
}

Quoi qu'il en soit ... maintenant que j'ai écrit du code, laissez-moi passer en revue les autres réponses et voir ce que j'ai manqué ... la première fois que je les ai numérisées. 

Anyway... now that I wrote some code, let me go through the other answers and see what I missed... the first time I scanned them.

10
RickHigh

LinkedHashMap est O (1), mais nécessite une synchronisation. Pas besoin de réinventer la roue là-bas.

2 options pour augmenter la simultanéité:

1 . Créez plusieurs LinkedHashMap et hachez-les en: Exemple: LinkedHashMap[4], index 0, 1, 2, 3. Sur la touche, faites key%4 (ou binary OR sur [key, 3]) pour choisir quelle carte effectuer/mettre/obtenir/supprimer.

2 . Vous pouvez faire une «presque» LRU en étendant ConcurrentHashMap et en ayant une carte de hachage liée comme une structure dans chacune des régions à l'intérieur de celle-ci. Le verrouillage se produirait de manière plus granulaire qu'une LinkedHashMap synchronisée. Sur put ou putIfAbsent, seul un verrou sur la tête et la queue de la liste est nécessaire (par région). Sur un retrait ou obtenir toute la région doit être verrouillé. Je suis curieux de savoir si les listes de liens Atomic pourraient être utiles ici - probablement pour la tête de liste. Peut-être pour plus.

La structure ne garderait pas la commande totale, mais seulement la commande par région. Tant que le nombre d'entrées est beaucoup plus grand que le nombre de régions, cela suffit pour la plupart des caches. Chaque région devra avoir son propre nombre d'entrées, qui serait utilisé plutôt que le nombre global pour le déclencheur d'éviction . Le nombre de régions par défaut dans une ConcurrentHashMap est de 16, ce qui est suffisant pour la plupart des serveurs aujourd'hui.

  1. serait plus facile à écrire et plus rapide avec une concurrence modérée.

  2. serait plus difficile à écrire, mais une échelle bien meilleure à très haute concurrence. Il serait plus lent pour un accès normal (tout comme ConcurrentHashMap est plus lent que HashMap en l'absence de simultanéité)

9
Scott

Il y a deux implémentations open source.

Apache Solr a ConcurrentLRUCache: https://lucene.Apache.org/solr/3_6_1/org/Apache/solr/util/ConcurrentLRUCache.html

Il existe un projet open source pour ConcurrentLinkedHashMap: http://code.google.com/p/concurrentlinkedhashmap/

8
Ron

J'envisagerais d'utiliser Java.util.concurrent.PriorityBlockingQueue , avec une priorité déterminée par un compteur "numberOfUses" dans chaque élément. Je serais très, très prudent pour que toute ma synchronisation soit correcte, car le compteur "numberOfUses" implique que l'élément ne peut être immuable.

L'objet element serait un wrapper pour les objets dans le cache:

class CacheElement {
    private final Object obj;
    private int numberOfUsers = 0;

    CacheElement(Object obj) {
        this.obj = obj;
    }

    ... etc.
}
7
Steve McLeod

J'espère que cela t'aides .

import Java.util.*;
public class Lru {

public static <K,V> Map<K,V> lruCache(final int maxSize) {
    return new LinkedHashMap<K, V>(maxSize*4/3, 0.75f, true) {

        private static final long serialVersionUID = -3588047435434569014L;

        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > maxSize;
        }
    };
 }
 public static void main(String[] args ) {
    Map<Object, Object> lru = Lru.lruCache(2);      
    lru.put("1", "1");
    lru.put("2", "2");
    lru.put("3", "3");
    System.out.println(lru);
}
}
6
murasing

Le cache LRU peut être implémenté à l'aide d'une ConcurrentLinkedQueue et d'un ConcurrentHashMap pouvant également être utilisés dans un scénario multithreading. La tête de la file d'attente est l'élément qui a été dans la file d'attente le plus longtemps. La queue de la file d'attente est cet élément qui a été dans la file d'attente le plus rapidement possible. Lorsqu'un élément existe dans la carte, nous pouvons le supprimer de la LinkedQueue et l'insérer à la fin.

import Java.util.concurrent.ConcurrentHashMap;
import Java.util.concurrent.ConcurrentLinkedQueue;

public class LRUCache<K,V> {
  private ConcurrentHashMap<K,V> map;
  private ConcurrentLinkedQueue<K> queue;
  private final int size; 

  public LRUCache(int size) {
    this.size = size;
    map = new ConcurrentHashMap<K,V>(size);
    queue = new ConcurrentLinkedQueue<K>();
  }

  public V get(K key) {
    //Recently accessed, hence move it to the tail
    queue.remove(key);
    queue.add(key);
    return map.get(key);
  }

  public void put(K key, V value) {
    //ConcurrentHashMap doesn't allow null key or values
    if(key == null || value == null) throw new NullPointerException();
    if(map.containsKey(key) {
      queue.remove(key);
    }
    if(queue.size() >= size) {
      K lruKey = queue.poll();
      if(lruKey != null) {
        map.remove(lruKey);
      }
    }
    queue.add(key);
    map.put(key,value);
  }

}
5
sanjanab

Voici mon implémentation pour LRU. J'ai utilisé PriorityQueue, qui fonctionne essentiellement comme FIFO et non pas threadsafe . Used Comparator basé sur la création de l'heure de la page et basé sur la commande Des pages pour l'heure la plus récemment utilisée.

Pages à prendre en considération: 2, 1, 0, 2, 8, 2, 4

La page ajoutée dans le cache est: 2
La page ajoutée dans le cache est: 1
La page ajoutée dans le cache est: 0
Page: 2 existe déjà dans le cache. Dernière heure d'accès mise à jour
Défaut de page, PAGE: 1, remplacé par PAGE: 8
La page ajoutée dans le cache est: 8
Page: 2 existe déjà dans le cache. Dernière heure d'accès mise à jour
Défaut de page, PAGE: 0, remplacé par PAGE: 4
La page ajoutée dans le cache est: 4

SORTIE

Pages LRUCache
-------------
PageName: 8, PageCreationTime: 1365957019974
PageName: 2, PageCreationTime: 1365957020074
PageName: 4, PageCreationTime: 1365957020174

entrez le code ici

import Java.util.Comparator;
import Java.util.Iterator;
import Java.util.PriorityQueue;


public class LRUForCache {
    private PriorityQueue<LRUPage> priorityQueue = new PriorityQueue<LRUPage>(3, new LRUPageComparator());
    public static void main(String[] args) throws InterruptedException {

        System.out.println(" Pages for consideration : 2, 1, 0, 2, 8, 2, 4");
        System.out.println("----------------------------------------------\n");

        LRUForCache cache = new LRUForCache();
        cache.addPageToQueue(new LRUPage("2"));
        Thread.sleep(100);
        cache.addPageToQueue(new LRUPage("1"));
        Thread.sleep(100);
        cache.addPageToQueue(new LRUPage("0"));
        Thread.sleep(100);
        cache.addPageToQueue(new LRUPage("2"));
        Thread.sleep(100);
        cache.addPageToQueue(new LRUPage("8"));
        Thread.sleep(100);
        cache.addPageToQueue(new LRUPage("2"));
        Thread.sleep(100);
        cache.addPageToQueue(new LRUPage("4"));
        Thread.sleep(100);

        System.out.println("\nLRUCache Pages");
        System.out.println("-------------");
        cache.displayPriorityQueue();
    }


    public synchronized void  addPageToQueue(LRUPage page){
        boolean pageExists = false;
        if(priorityQueue.size() == 3){
            Iterator<LRUPage> iterator = priorityQueue.iterator();

            while(iterator.hasNext()){
                LRUPage next = iterator.next();
                if(next.getPageName().equals(page.getPageName())){
                    /* wanted to just change the time, so that no need to poll and add again.
                       but elements ordering does not happen, it happens only at the time of adding
                       to the queue

                       In case somebody finds it, plz let me know.
                     */
                    //next.setPageCreationTime(page.getPageCreationTime()); 

                    priorityQueue.remove(next);
                    System.out.println("Page: " + page.getPageName() + " already exisit in cache. Last accessed time updated");
                    pageExists = true;
                    break;
                }
            }
            if(!pageExists){
                // enable it for printing the queue elemnts
                //System.out.println(priorityQueue);
                LRUPage poll = priorityQueue.poll();
                System.out.println("Page Fault, PAGE: " + poll.getPageName()+", Replaced with PAGE: "+page.getPageName());

            }
        }
        if(!pageExists){
            System.out.println("Page added into cache is : " + page.getPageName());
        }
        priorityQueue.add(page);

    }

    public void displayPriorityQueue(){
        Iterator<LRUPage> iterator = priorityQueue.iterator();
        while(iterator.hasNext()){
            LRUPage next = iterator.next();
            System.out.println(next);
        }
    }
}

class LRUPage{
    private String pageName;
    private long pageCreationTime;
    public LRUPage(String pagename){
        this.pageName = pagename;
        this.pageCreationTime = System.currentTimeMillis();
    }

    public String getPageName() {
        return pageName;
    }

    public long getPageCreationTime() {
        return pageCreationTime;
    }

    public void setPageCreationTime(long pageCreationTime) {
        this.pageCreationTime = pageCreationTime;
    }

    @Override
    public boolean equals(Object obj) {
        LRUPage page = (LRUPage)obj; 
        if(pageCreationTime == page.pageCreationTime){
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return (int) (31 * pageCreationTime);
    }

    @Override
    public String toString() {
        return "PageName: " + pageName +", PageCreationTime: "+pageCreationTime;
    }
}


class LRUPageComparator implements Comparator<LRUPage>{

    @Override
    public int compare(LRUPage o1, LRUPage o2) {
        if(o1.getPageCreationTime() > o2.getPageCreationTime()){
            return 1;
        }
        if(o1.getPageCreationTime() < o2.getPageCreationTime()){
            return -1;
        }
        return 0;
    }
}
3
Deepak Singhvi

Eh bien, pour un cache, vous rechercherez généralement des données via un objet proxy (une URL, une chaîne, etc.) afin que vous souhaitiez une carte au niveau de l'interface. mais pour mettre les choses à plat, vous voulez une file d’attente semblable à une structure. En interne, je maintiendrais deux structures de données, une file d'attente prioritaire et une carte de hachage. Voici une implémentation qui devrait pouvoir tout faire dans O(1) time.

Voici un cours que j'ai rapidement préparé:

import Java.util.HashMap;
import Java.util.Map;
public class LRUCache<K, V>
{
    int maxSize;
    int currentSize = 0;

    Map<K, ValueHolder<K, V>> map;
    LinkedList<K> queue;

    public LRUCache(int maxSize)
    {
        this.maxSize = maxSize;
        map = new HashMap<K, ValueHolder<K, V>>();
        queue = new LinkedList<K>();
    }

    private void freeSpace()
    {
        K k = queue.remove();
        map.remove(k);
        currentSize--;
    }

    public void put(K key, V val)
    {
        while(currentSize >= maxSize)
        {
            freeSpace();
        }
        if(map.containsKey(key))
        {//just heat up that item
            get(key);
            return;
        }
        ListNode<K> ln = queue.add(key);
        ValueHolder<K, V> rv = new ValueHolder<K, V>(val, ln);
        map.put(key, rv);       
        currentSize++;
    }

    public V get(K key)
    {
        ValueHolder<K, V> rv = map.get(key);
        if(rv == null) return null;
        queue.remove(rv.queueLocation);
        rv.queueLocation = queue.add(key);//this ensures that each item has only one copy of the key in the queue
        return rv.value;
    }
}

class ListNode<K>
{
    ListNode<K> prev;
    ListNode<K> next;
    K value;
    public ListNode(K v)
    {
        value = v;
        prev = null;
        next = null;
    }
}

class ValueHolder<K,V>
{
    V value;
    ListNode<K> queueLocation;
    public ValueHolder(V value, ListNode<K> ql)
    {
        this.value = value;
        this.queueLocation = ql;
    }
}

class LinkedList<K>
{
    ListNode<K> head = null;
    ListNode<K> tail = null;

    public ListNode<K> add(K v)
    {
        if(head == null)
        {
            assert(tail == null);
            head = tail = new ListNode<K>(v);
        }
        else
        {
            tail.next = new ListNode<K>(v);
            tail.next.prev = tail;
            tail = tail.next;
            if(tail.prev == null)
            {
                tail.prev = head;
                head.next = tail;
            }
        }
        return tail;
    }

    public K remove()
    {
        if(head == null)
            return null;
        K val = head.value;
        if(head.next == null)
        {
            head = null;
            tail = null;
        }
        else
        {
            head = head.next;
            head.prev = null;
        }
        return val;
    }

    public void remove(ListNode<K> ln)
    {
        ListNode<K> prev = ln.prev;
        ListNode<K> next = ln.next;
        if(prev == null)
        {
            head = next;
        }
        else
        {
            prev.next = next;
        }
        if(next == null)
        {
            tail = prev;
        }
        else
        {
            next.prev = prev;
        }       
    }
}

Voici comment ça fonctionne. Les clés sont stockées dans une liste chaînée avec les clés les plus anciennes au début de la liste (les nouvelles clés vont au dos). Ainsi, lorsque vous devez "éjecter" quelque chose, il suffit de le placer en tête de la file d'attente, puis d'utiliser supprimer la valeur de la carte. Lorsqu'un élément est référencé, vous extrayez le ValueHolder de la carte, puis utilisez la variable de file d'attente pour supprimer la clé de son emplacement actuel dans la file d'attente, puis placez-la à l'arrière de la file d'attente (la plus récemment utilisée). Ajouter des choses est à peu près la même chose.

Je suis sûr qu'il y a une tonne d'erreurs ici et je n'ai mis en œuvre aucune synchronisation. mais cette classe fournira O(1) ajout au cache, O(1) suppression des anciens éléments et O(1) récupération des éléments du cache . Même une synchronisation triviale (il suffit de synchroniser toutes les méthodes publiques) aurait encore peu de conflits de verrous en raison du temps d'exécution. Si quelqu'un a des astuces de synchronisation intelligentes, je serais très intéressé. De plus, je suis sûr que vous pouvez implémenter d'autres optimisations à l'aide de la variable maxsize par rapport à la carte.

2
luke

C’est le cache LRU que j’utilise, qui encapsule un LinkedHashMap et gère les accès simultanés avec un simple verrou de synchronisation pour protéger les points juteux. Il "touche" les éléments au fur et à mesure qu'ils sont utilisés pour qu'ils redeviennent l'élément "le plus frais", de sorte qu'il s'agisse en réalité de LRU. J'avais également l'obligation de mes éléments d'avoir une durée de vie minimale, que vous pouvez également considérer comme "le temps d'inactivité maximum", alors vous êtes prêt pour l'expulsion.

Cependant, je suis d'accord avec la conclusion de Hank et j'ai accepté sa réponse: si je recommençais aujourd'hui, je vérifierais la variable CacheBuilder de Guava.

import Java.util.HashMap;
import Java.util.LinkedHashMap;
import Java.util.Map;


public class MaxIdleLRUCache<KK, VV> {

    final static private int IDEAL_MAX_CACHE_ENTRIES = 128;

    public interface DeadElementCallback<KK, VV> {
        public void notify(KK key, VV element);
    }

    private Object lock = new Object();
    private long minAge;
    private HashMap<KK, Item<VV>> cache;


    public MaxIdleLRUCache(long minAgeMilliseconds) {
        this(minAgeMilliseconds, IDEAL_MAX_CACHE_ENTRIES);
    }

    public MaxIdleLRUCache(long minAgeMilliseconds, int idealMaxCacheEntries) {
        this(minAgeMilliseconds, idealMaxCacheEntries, null);
    }

    public MaxIdleLRUCache(long minAgeMilliseconds, int idealMaxCacheEntries, final DeadElementCallback<KK, VV> callback) {
        this.minAge = minAgeMilliseconds;
        this.cache = new LinkedHashMap<KK, Item<VV>>(IDEAL_MAX_CACHE_ENTRIES + 1, .75F, true) {
            private static final long serialVersionUID = 1L;

            // This method is called just after a new entry has been added
            public boolean removeEldestEntry(Map.Entry<KK, Item<VV>> eldest) {
                // let's see if the oldest entry is old enough to be deleted. We don't actually care about the cache size.
                long age = System.currentTimeMillis() - eldest.getValue().birth;
                if (age > MaxIdleLRUCache.this.minAge) {
                    if ( callback != null ) {
                        callback.notify(eldest.getKey(), eldest.getValue().payload);
                    }
                    return true; // remove it
                }
                return false; // don't remove this element
            }
        };

    }

    public void put(KK key, VV value) {
        synchronized ( lock ) {
//          System.out.println("put->"+key+","+value);
            cache.put(key, new Item<VV>(value));
        }
    }

    public VV get(KK key) {
        synchronized ( lock ) {
//          System.out.println("get->"+key);
            Item<VV> item = getItem(key);
            return item == null ? null : item.payload;
        }
    }

    public VV remove(String key) {
        synchronized ( lock ) {
//          System.out.println("remove->"+key);
            Item<VV> item =  cache.remove(key);
            if ( item != null ) {
                return item.payload;
            } else {
                return null;
            }
        }
    }

    public int size() {
        synchronized ( lock ) {
            return cache.size();
        }
    }

    private Item<VV> getItem(KK key) {
        Item<VV> item = cache.get(key);
        if (item == null) {
            return null;
        }
        item.touch(); // idle the item to reset the timeout threshold
        return item;
    }

    private static class Item<T> {
        long birth;
        T payload;

        Item(T payload) {
            this.birth = System.currentTimeMillis();
            this.payload = payload;
        }

        public void touch() {
            this.birth = System.currentTimeMillis();
        }
    }

}
2
broc.seib

Voici l'implémentation de cache LRU simultanée la plus performante que j'ai testée sans bloc synchronisé:

public class ConcurrentLRUCache<Key, Value> {

private final int maxSize;

private ConcurrentHashMap<Key, Value> map;
private ConcurrentLinkedQueue<Key> queue;

public ConcurrentLRUCache(final int maxSize) {
    this.maxSize = maxSize;
    map = new ConcurrentHashMap<Key, Value>(maxSize);
    queue = new ConcurrentLinkedQueue<Key>();
}

/**
 * @param key - may not be null!
 * @param value - may not be null!
 */
public void put(final Key key, final Value value) {
    if (map.containsKey(key)) {
        queue.remove(key); // remove the key from the FIFO queue
    }

    while (queue.size() >= maxSize) {
        Key oldestKey = queue.poll();
        if (null != oldestKey) {
            map.remove(oldestKey);
        }
    }
    queue.add(key);
    map.put(key, value);
}

/**
 * @param key - may not be null!
 * @return the value associated to the given key or null
 */
public Value get(final Key key) {
    return map.get(key);
}

}

2
Zoltan Boda

Voici ma courte mise en œuvre, merci de la critiquer ou de l'améliorer!

package util.collection;

import Java.util.concurrent.ConcurrentHashMap;
import Java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Limited size concurrent cache map implementation.<br/>
 * LRU: Least Recently Used.<br/>
 * If you add a new key-value pair to this cache after the maximum size has been exceeded,
 * the oldest key-value pair will be removed before adding.
 */

public class ConcurrentLRUCache<Key, Value> {

private final int maxSize;
private int currentSize = 0;

private ConcurrentHashMap<Key, Value> map;
private ConcurrentLinkedQueue<Key> queue;

public ConcurrentLRUCache(final int maxSize) {
    this.maxSize = maxSize;
    map = new ConcurrentHashMap<Key, Value>(maxSize);
    queue = new ConcurrentLinkedQueue<Key>();
}

private synchronized void freeSpace() {
    Key key = queue.poll();
    if (null != key) {
        map.remove(key);
        currentSize = map.size();
    }
}

public void put(Key key, Value val) {
    if (map.containsKey(key)) {// just heat up that item
        put(key, val);
        return;
    }
    while (currentSize >= maxSize) {
        freeSpace();
    }
    synchronized(this) {
        queue.add(key);
        map.put(key, val);
        currentSize++;
    }
}

public Value get(Key key) {
    return map.get(key);
}
}
1
Zoltan Boda

Voici ma propre implémentation à ce problème

simplelrucache fournit une mise en cache LRU extrêmement simple et threadsafe, avec un support TTL. Il fournit deux implémentations:

  • Concurrent basé sur ConcurrentLinkedHashMap
  • Synchronisé sur LinkedHashMap

Vous pouvez le trouver ici: http://code.google.com/p/simplelrucache/

1
Daimon

Regardez ConcurrentSkipListMap . Cela devrait vous donner le temps de log (n) pour tester et supprimer un élément s'il est déjà contenu dans le cache, et un temps constant pour le rajouter.

Vous aurez simplement besoin de certains éléments compteur, etc. et wrapper pour forcer la commande de la commande LRU et vous assurer que les éléments récents sont ignorés lorsque le cache est plein.

1
madlep

Suivant le concept de @sanjanab (mais après des correctifs), j'ai créé ma version de LRUCache en fournissant également le consommateur qui permet de faire quelque chose avec les éléments supprimés si nécessaire.

public class LRUCache<K, V> {

    private ConcurrentHashMap<K, V> map;
    private final Consumer<V> onRemove;
    private ConcurrentLinkedQueue<K> queue;
    private final int size;

    public LRUCache(int size, Consumer<V> onRemove) {
        this.size = size;
        this.onRemove = onRemove;
        this.map = new ConcurrentHashMap<>(size);
        this.queue = new ConcurrentLinkedQueue<>();
    }

    public V get(K key) {
        //Recently accessed, hence move it to the tail
        if (queue.remove(key)) {
            queue.add(key);
            return map.get(key);
        }
        return null;
    }

    public void put(K key, V value) {
        //ConcurrentHashMap doesn't allow null key or values
        if (key == null || value == null) throw new IllegalArgumentException("key and value cannot be null!");

        V existing = map.get(key);
        if (existing != null) {
            queue.remove(key);
            onRemove.accept(existing);
        }

        if (map.size() >= size) {
            K lruKey = queue.poll();
            if (lruKey != null) {
                V removed = map.remove(lruKey);
                onRemove.accept(removed);
            }
        }
        queue.add(key);
        map.put(key, value);
    }
}
0
Aleksander Lech

Voulait ajouter un commentaire à la réponse donnée par Hank mais certains comment je ne suis pas en mesure de - traitez-le comme s'il vous plaît 

LinkedHashMap conserve également l'ordre des accès en fonction du paramètre transmis dans son constructeur Il conserve une liste doublée pour maintenir l'ordre (voir LinkedHashMap.Entry)

@Pacerier il est correct que LinkedHashMap garde le même ordre pendant l'itération si l'élément est ajouté à nouveau, mais ce n'est qu'en cas de mode d'ordre d'insertion.

c'est ce que j'ai trouvé dans la documentation Java de l'objet LinkedHashMap.Entry 

    /**
     * This method is invoked by the superclass whenever the value
     * of a pre-existing entry is read by Map.get or modified by Map.set.
     * If the enclosing Map is access-ordered, it moves the entry
     * to the end of the list; otherwise, it does nothing.
     */
    void recordAccess(HashMap<K,V> m) {
        LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
        if (lm.accessOrder) {
            lm.modCount++;
            remove();
            addBefore(lm.header);
        }
    }

cette méthode prend en charge le déplacement des éléments récemment consultés vers la fin de la liste. Donc dans l’ensemble, LinkedHashMap est la meilleure structure de données pour implémenter LRUCache.

0
Abhishek Gayakwad

Une autre pensée et même une simple implémentation utilisant la collection LinkedHashMap de Java.

LinkedHashMap a fourni la méthode removeEldestEntry et peut être remplacée de la manière indiquée dans l'exemple. Par défaut, l'implémentation de cette structure de collection est false. Si sa taille réelle et la taille de cette structure dépasse la capacité initiale, les éléments les plus anciens ou les plus anciens seront supprimés. 

Nous pouvons avoir un contenu de page et de pageno dans mon cas, pageno est un entier et une page que j'ai conservée.

import Java.util.LinkedHashMap;
import Java.util.Map;

/**
 * @author Deepak Singhvi
 *
 */
public class LRUCacheUsingLinkedHashMap {


     private static int CACHE_SIZE = 3;
     public static void main(String[] args) {
        System.out.println(" Pages for consideration : 2, 1, 0, 2, 8, 2, 4,99");
        System.out.println("----------------------------------------------\n");


// accessOrder is true, so whenever any page gets changed or accessed,    // its order will change in the map, 
              LinkedHashMap<Integer,String> lruCache = new              
                 LinkedHashMap<Integer,String>(CACHE_SIZE, .75F, true) {

           private static final long serialVersionUID = 1L;

           protected boolean removeEldestEntry(Map.Entry<Integer,String>                           

                     eldest) {
                          return size() > CACHE_SIZE;
                     }

                };

  lruCache.put(2, "2");
  lruCache.put(1, "1");
  lruCache.put(0, "0");
  System.out.println(lruCache + "  , After first 3 pages in cache");
  lruCache.put(2, "2");
  System.out.println(lruCache + "  , Page 2 became the latest page in the cache");
  lruCache.put(8, "8");
  System.out.println(lruCache + "  , Adding page 8, which removes eldest element 2 ");
  lruCache.put(2, "2");
  System.out.println(lruCache+ "  , Page 2 became the latest page in the cache");
  lruCache.put(4, "4");
  System.out.println(lruCache+ "  , Adding page 4, which removes eldest element 1 ");
  lruCache.put(99, "99");
  System.out.println(lruCache + " , Adding page 99, which removes eldest element 8 ");

     }

}

Le résultat de l'exécution du code ci-dessus est le suivant:

 Pages for consideration : 2, 1, 0, 2, 8, 2, 4,99
--------------------------------------------------
    {2=2, 1=1, 0=0}  , After first 3 pages in cache
    {2=2, 1=1, 0=0}  , Page 2 became the latest page in the cache
    {1=1, 0=0, 8=8}  , Adding page 8, which removes eldest element 2 
    {0=0, 8=8, 2=2}  , Page 2 became the latest page in the cache
    {8=8, 2=2, 4=4}  , Adding page 4, which removes eldest element 1 
    {2=2, 4=4, 99=99} , Adding page 99, which removes eldest element 8 
0
Deepak Singhvi

Je recherche un meilleur cache LRU utilisant du code Java. Est-il possible pour vous de partager votre code de cache Java LRU en utilisant LinkedHashMap et Collections#synchronizedMap? Actuellement, j'utilise LRUMap implements Map et le code fonctionne bien, mais je reçois ArrayIndexOutofBoundException lors de tests de charge utilisant 500 utilisateurs selon la méthode ci-dessous. La méthode déplace l'objet récent vers l'avant de la file d'attente.

private void moveToFront(int index) {
        if (listHead != index) {
            int thisNext = nextElement[index];
            int thisPrev = prevElement[index];
            nextElement[thisPrev] = thisNext;
            if (thisNext >= 0) {
                prevElement[thisNext] = thisPrev;
            } else {
                listTail = thisPrev;
            }
            //old listHead and new listHead say new is 1 and old was 0 then prev[1]= 1 is the head now so no previ so -1
            // prev[0 old head] = new head right ; next[new head] = old head
            prevElement[index] = -1;
            nextElement[index] = listHead;
            prevElement[listHead] = index;
            listHead = index;
        }
    }

Les méthodes get(Object key) et put(Object key, Object value) appellent la méthode moveToFront ci-dessus.

0
Raj Pandian