Jak zaimplementowałbyś pamięć podręczną LRU w Javie?

Proszę nie mówić EHCache czy OSCache itp. Załóżmy dla celów tego pytania, że chcę zaimplementować własne za pomocą tylko SDK (uczenie się przez działanie). Biorąc pod uwagę, że pamięć podręczna będzie używana w środowisku wielowątkowym, jakich struktur danych użyjesz? Zaimplementowałem już jedną przy użyciu LinkedHashMap i Kolekcje#synchronizedMap, ale jestem ciekaw, czy któraś z nowych współbieżnych kolekcji byłaby lepszym kandydatem.

UPDATE: właśnie czytałam Najnowszy yegge kiedy znalazłem ten samorodek:

Jeśli potrzebujesz stałego dostępu i chcesz utrzymać kolejność wstawiania, nie możesz zrobić nic lepszego niż LinkedHashMap, naprawdę wspaniała struktura danych. Jedynym sposobem, aby być ewentualnie może być bardziej wspaniałe jest, jeśli istnieje równoległa wersja. Ale niestety.

Myślałem prawie dokładnie to samo, zanim poszedłem z LinkedHashMap + Collections#synchronizedMap implementacja, o której wspomniałem powyżej. Miło wiedzieć, że nie przeoczyłem coś.

Opierając się na dotychczasowych odpowiedziach, wydaje się, że moim najlepszym rozwiązaniem dla wysoce współbieżnego LRU byłoby rozszerzenie ConcurrentHashMap przy użyciu tej samej logiki, której używa LinkedHashMap.

Author: Mifeet, 2008-10-21

20 answers

Lubię wiele tych propozycji, ale na razie myślę, że zostanę przy LinkedHashMap + Collections.synchronizedMap. Jeśli wrócę do tego w przyszłości, prawdopodobnie popracuję nad rozszerzeniem ConcurrentHashMap w ten sam sposób LinkedHashMap rozszerza HashMap.

Aktualizacja:

Na życzenie, oto streszczenie mojej obecnej realizacji.

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));
 97
Author: Hank Gay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-10 16:36:01
 17
Author: Joe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-05 15:58:39

Gdybym dziś robił to jeszcze raz od zera, użyłbym Guava ' S CacheBuilder.

 12
Author: Hank Gay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-16 16:56:36

To druga runda.

Pierwsza runda była tym, co wymyśliłem, a potem ponownie przeczytałem komentarze z domeną nieco bardziej zakorzenioną w mojej głowie.

Oto najprostsza wersja z testem jednostkowym, który pokazuje, że działa w oparciu o inne wersje.

Pierwsza wersja nie współbieżna:

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


}

Prawdziwa flaga będzie śledzić dostęp do gets i puts. Zobacz JavaDocs. RemoveEdelstEntry bez prawdziwej flagi do konstruktora po prostu zaimplementowałby FIFO cache (patrz UWAGI poniżej na FIFO i removeEldestEntry).

Oto test, który udowodni, że działa jako pamięć podręczna 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 ();

    }

Teraz dla wersji współbieżnej...

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


}

Możesz zobaczyć, dlaczego najpierw pokrywam wersję Nie-współbieżną. Powyższe próby tworzenia pasków w celu zmniejszenia sporu blokady. Więc my to hashuje klucz, a następnie wyszukuje ten hash, aby znaleźć rzeczywistą pamięć podręczną. To sprawia, że rozmiar limitu jest bardziej sugestią/zgadywanką w ramach sporej ilości błędów w zależności od tego, jak dobrze rozprzestrzenił się algorytm hashowania kluczy.

Oto test pokazujący, że wersja współbieżna prawdopodobnie działa. :) (Test pod ostrzałem byłby prawdziwy).

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



    }

}

To jest Ostatni post.. Pierwszy post usunąłem, ponieważ był to LFU, a nie Pamięć podręczna LRU.

Pomyślałem, że spróbuję jeszcze raz. Próbowałem wymyślić najprostszą wersję pamięci podręcznej LRU przy użyciu standardowego JDK bez zbyt dużej implementacji. Oto, co wymyśliłem. Moja pierwsza próba była trochę katastrofą, ponieważ zaimplementowałem LFU zamiast i LRU, a następnie dodałem do niego wsparcie FIFO i LRU... i wtedy zdałem sobie sprawę, że to staje się potworem. Potem zacząłem rozmawiać z moim kumplem Johnem, który był ledwo zainteresowany, a potem opisałem głęboko, jak zaimplementowałem LFU, LRU i FIFO i jak można go przełączyć za pomocą prostego ARG ENUM, a potem zdałem sobie sprawę, że wszystko, czego naprawdę chciałem, to prosty LRU. Więc zignoruj wcześniejszy post ode mnie i daj mi znać, jeśli chcesz zobaczyć pamięć podręczną LRU/LFU / FIFO, którą można przełączać za pomocą enum... nie? Ok.. proszę bardzo.

Najprostszy możliwy LRU przy użyciu tylko JDK. Zaimplementowałem zarówno wersję współbieżną, jak i nie współbieżną.

Stworzyłem wspólny interfejs (jest to minimalizm, więc prawdopodobnie brakuje kilku funkcji, które chcesz, ale to działa dla moich przypadków użycia, ale niech jeśli chcesz zobaczyć funkcję XYZ daj mi znać... Żyję by pisać kod.).

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

Możesz się zastanawiać, co getSilent jest. Używam tego do testów. getSilent nie zmienia wyniku LRU przedmiotu.

Najpierw nie-równoległe....

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

Kolejka.removeFirstOccurrence jest potencjalnie kosztowną operacją, jeśli masz dużą pamięć podręczną. Można wziąć LinkedList jako przykład i dodać odwrotną mapę hashową od elementu do węzła, aby operacje usuwania były o wiele szybsze i bardziej spójne. Zacząłem też, ale potem zdałem sobie sprawę, że go nie potrzebuję. Ale... może...

Kiedy put zostanie wywołany, klucz zostanie dodany do kolejki. Kiedy get zostanie wywołany, klucz zostanie usunięty i ponownie dodany do górnej części kolejki.

Jeśli twoja pamięć podręczna jest mała, a budowa elementu jest droga, to powinna to być dobra pamięć podręczna. Jeśli twoja pamięć podręczna jest naprawdę duża, wyszukiwanie liniowe może być szyjką butelki, zwłaszcza jeśli nie masz gorących obszarów pamięci podręcznej. Im więcej intensywne gorące punkty, tym szybciej wyszukiwanie liniowe, ponieważ gorące przedmioty są zawsze na szczycie wyszukiwania liniowego. W każdym razie... to, co jest potrzebne do tego, aby iść szybciej, to napisać inną LinkedList, która ma operację usuwania, która ma element odwrotny do node lookup dla usuń, a następnie usunięcie byłoby tak szybkie, jak usunięcie klucza z mapy hash.

Jeśli masz pamięć podręczną poniżej 1000 pozycji, powinno to zadziałać dobrze.

Oto prosty test, aby pokazać swoje operacje w akcji.

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

    }
}

Ostatnia pamięć podręczna LRU była jednowątkowa i proszę nie owijać jej w nic zsynchronizowanego....

Tutaj jest dźgnięcie w wersji równoległej.

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

Główne różnice to użycie ConcurrentHashMap zamiast HashMap i użycie blokady (mogłem uciec z synchronized, ale...).

Nie testowałem go pod ogniem, ale wydaje się, że jest to prosta pamięć podręczna LRU, która może działać w 80% przypadków użycia, gdzie potrzebujesz prostego LRU Mapa.

Mile widziane opinie, z wyjątkiem dlaczego nie używasz biblioteki a, b lub C. Powodem, dla którego nie zawsze używam biblioteki, jest to, że nie zawsze chcę, aby każdy plik wojenny miał 80MB, a piszę biblioteki, więc zwykle robię wtyczkę libs z wystarczająco dobrym rozwiązaniem i ktoś może podłączyć innego dostawcę pamięci podręcznej, jeśli chce. :) Nigdy nie wiem, kiedy ktoś może potrzebować Guava lub ehcache lub czegoś innego, nie chcę ich włączać, ale jeśli zrobię buforowanie plug-able, Nie będę / align = "left" /

Redukcja zależności ma swoją nagrodę. Uwielbiam uzyskać opinie na temat tego, jak to jeszcze prostsze lub szybsze lub jedno i drugie.

Również, jeśli ktoś zna gotowy do pracy....

Ok.. Wiem, co sobie myślisz... Dlaczego po prostu nie używa removeEldest entry z LinkedHashMap, i cóż powinienem ale.... ale.. ale.. To byłoby FIFO, a nie LRU i próbowaliśmy wdrożyć LRU.

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

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

Ten test nie powiódł się dla powyższego kod...

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

Oto szybka i brudna pamięć podręczna FIFO przy użyciu removeEldestEntry.

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

FIFO są szybkie. Żadnego szukania. Mógłbyś przestawić FIFO przed LRU i to całkiem ładnie poradziłoby sobie z większością gorących wpisów. Lepszy LRU będzie potrzebował tego elementu odwrotnego do funkcji węzła.

W każdym razie... teraz, gdy napisałem jakiś kod, pozwól mi przejrzeć inne odpowiedzi i zobaczyć, co przegapiłem... pierwszy raz je zeskanowałem.
 9
Author: RickHigh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-18 07:51:03

LinkedHashMap jest O (1), ale wymaga synchronizacji. Nie ma potrzeby odkrywania koła na nowo.

2 opcje zwiększenia współbieżności:

1. Utwórz wiele LinkedHashMap i hashuj do nich: przykład: LinkedHashMap[4], index 0, 1, 2, 3. Na klawiszu do key%4 (lub binary OR on [key, 3]), aby wybrać mapę do zrobienia put / get/remove.

2. Możesz zrobić "prawie" LRU, rozszerzając ConcurrentHashMap i mając połączoną mapę hashową w każdym z regionów wewnątrz niej. Blokowanie wystąpiłoby bardziej ziarnisto niż LinkedHashMap to jest zsynchronizowane. Na put lub putIfAbsent potrzebna jest tylko blokada na głowie i ogonie listy (na region). Na usunąć lub uzyskać cały region musi być zablokowany. Jestem ciekaw, czy jakieś listy linkowane mogłyby tu pomóc. pewnie tak dla szefa listy. Może za więcej.

Struktura nie utrzymałaby całkowitej kolejności, a jedynie kolejność według regionu. Tak długo, jak liczba wpisów jest znacznie większa niż liczba regionów, jest to wystarczająco dobre dla większości pamięci podręcznych. Każdy region będzie musiał mieć własną liczbę wpisów, będzie ona używana zamiast globalnej liczby dla wyzwalacza eksmisji. Domyślna liczba regionów w ConcurrentHashMap to 16, co jest wystarczające dla większości serwerów.

  1. Byłoby łatwiej pisać i szybciej przy umiarkowanej współbieżności.

  2. Byłoby trudniej napisać, ale znacznie lepiej skalować przy bardzo wysokiej współbieżności. Będzie wolniejszy dla normalnego dostępu (tak jak {[6] } jest wolniejszy niż HashMap gdzie jest brak współbieżności)

 8
Author: jiaweizhang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-24 16:19:59

Istnieją dwie implementacje open source.

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

Jest projekt open source dla ConcurrentLinkedHashMap: http://code.google.com/p/concurrentlinkedhashmap/

 8
Author: Ron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-17 04:10:22

Rozważyłbym użycie Javy.util./ align = "left" / PriorityBlockingQueue , z priorytetem określonym przez licznik "numerofuses" w każdym elemencie. I would be bardzo, bardzo ostrożnie aby cała moja synchronizacja była poprawna, ponieważ licznik "numerofuses" sugeruje, że element nie może być niezmienny.

Obiekt element będzie opakowaniem dla obiektów w pamięci podręcznej:

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

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

    ... etc.
}
 7
Author: Steve McLeod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-21 11:41:20

Pamięć podręczna LRU może być zaimplementowana przy użyciu ConcurrentLinkedQueue i ConcurrentHashMap, które mogą być również używane w scenariuszu wielowątkowym. Głowa kolejki to ten element, który był w kolejce najdłużej. Ogon kolejki to ten element, który był w kolejce najkrótszym czasie. Gdy element istnieje na mapie, możemy go usunąć z LinkedQueue i wstawić go na ogonie.

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

}
 6
Author: sanjanab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-06 08:41:23

Mam nadzieję, że to pomoże .

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
Author: murasing,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-28 05:01:33

Oto moja implementacja dla LRU. Użyłem PriorityQueue, który w zasadzie działa jako FIFO, a nie threadsafe. Używany komparator na podstawie czasu tworzenia strony i na podstawie realizacji zamówienia z ostatnio używanych stron.

Strony do rozważenia : 2, 1, 0, 2, 8, 2, 4

Strona dodana do cache to: 2
Strona dodana do cache to: 1
Strona dodana do cache to: 0
Strona: 2 już exisit w pamięci podręcznej. Ostatnia aktualizacja
Strona Fault, PAGE: 1, REPLACE with PAGE: 8
Strona dodana do cache to: 8
Strona: 2 już exisit w pamięci podręcznej. Ostatnia aktualizacja
Strona: 0, Strona: 4
Strona dodana do cache jest : 4

Wyjście

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

Wpisz tutaj kod

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
Author: Deepak Singhvi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-14 16:36:01

Oto moja przetestowana, najlepiej działająca implementacja współbieżnej pamięci podręcznej LRU bez żadnego zsynchronizowanego bloku:

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
Author: Zoltan Boda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-26 11:46:44

Jest to pamięć podręczna LRU, której używam, która zawiera LinkedHashMap i obsługuje współbieżność za pomocą prostej blokady synchronizacji chroniącej soczyste miejsca. "Dotyka" elementów, ponieważ są one używane, aby ponownie stały się "najświeższym" elementem, tak że jest to rzeczywiście LRU. Miałem również wymóg, aby moje elementy miały minimalną żywotność, co można również myśleć o "maksymalnym czasie bezczynności" dozwolone, to jesteś do eksmisji.

Zgadzam się jednak z wnioskiem Hanka i akceptuję odpowiedź-gdybym zaczął to jeszcze dzisiaj, sprawdziłbym Guava ' s CacheBuilder.
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
Author: broc.seib,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-21 17:02:40

Cóż, dla pamięci podręcznej zazwyczaj będziesz szukał jakiegoś fragmentu danych za pośrednictwem obiektu proxy, (URL, String....). ale żeby coś wykopać trzeba mieć taką kolejkę jak struktura. Wewnętrznie chciałbym zachować dwie struktury danych, priorytet-Kolejka i HashMap. oto implementacja, która powinna być w stanie zrobić wszystko w O(1) czasie.

Oto klasa, którą dość szybko podniosłem:

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;
        }       
    }
}
Oto jak to działa. Klucze są przechowywane w lista połączona z najstarszymi klawiszami z przodu listy (nowe klawisze idą z tyłu), więc gdy musisz coś "wysunąć", po prostu wyrzuć to z przodu kolejki, a następnie użyj klawisza, aby usunąć wartość z mapy. Gdy element zostanie odwołany, chwytasz ValueHolder z mapy, a następnie używasz zmiennej queuelocation, aby usunąć klucz z jego bieżącej lokalizacji w kolejce, a następnie umieścić go z tyłu kolejki (jest teraz ostatnio używany). Dodawanie rzeczy jest w zasadzie to samo.

Jestem pewien, że jest tu mnóstwo błędów i nie zaimplementowałem żadnej synchronizacji. ale ta klasa zapewni o(1) dodawanie do bufora, o(1) usuwanie starych elementów i o (1) Odzyskiwanie elementów bufora. Nawet trywialna synchronizacja (wystarczy zsynchronizować każdą metodę publiczną) nadal będzie miała mały problem z blokadą ze względu na czas wykonywania. Jeśli ktoś ma jakieś sprytne sztuczki synchronizacyjne byłbym bardzo zainteresowany. Ponadto, jestem pewien, że istnieją dodatkowe optymalizacje, które można zaimplementuj przy użyciu zmiennej maxsize w odniesieniu do mapy.

 2
Author: luke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-29 11:23:06

Zobacz

ConcurrentSkipListMap. Powinien dać ci czas log(N) na testowanie i usuwanie elementu, jeśli jest on już zawarty w pamięci podręcznej, oraz stały czas na ponowne dodanie go.

Potrzebujesz tylko licznika etc i elementu wrappera, aby wymusić kolejność LRU i upewnić się, że ostatnie rzeczy są odrzucane, gdy bufor jest pełny.

 1
Author: madlep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-21 12:17:15

Oto moja krótka implementacja, proszę o krytykę lub poprawienie!

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
Author: Zoltan Boda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-07 23:49:30

Oto moja własna implementacja tego problemu

Simplelrucache zapewnia threadsafe, bardzo proste, nie rozproszone buforowanie LRU z obsługą TTL. Zawiera dwie implementacje:

  • współbieżne na podstawie ConcurrentLinkedHashMap
  • Synchronized based on LinkedHashMap

Znajdziesz go tutaj: http://code.google.com/p/simplelrucache/

 1
Author: Daimon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-21 12:18:24

Szukam lepszej pamięci podręcznej LRU przy użyciu kodu Java. Czy możesz udostępnić swój kod pamięci podręcznej Java LRU używając LinkedHashMap i Collections#synchronizedMap? Obecnie używam LRUMap implements Map i Kod działa dobrze, ale dostaję ArrayIndexOutofBoundException na testowanie obciążenia przy użyciu 500 użytkowników na poniższej metodzie. Metoda przenosi ostatni obiekt na początek kolejki.

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

get(Object key) i put(Object key, Object value) metoda wywołuje powyższą metodę moveToFront.

 0
Author: Raj Pandian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 13:08:50

Chciałem dodać komentarz do odpowiedzi udzielonej przez Hanka ale trochę jak nie jestem w stanie-proszę traktować to jako komentarz

LinkedHashMap utrzymuje kolejność dostępu również na podstawie parametru przekazanego w konstruktorze Utrzymuje podwójną listę w linii, aby utrzymać porządek (Patrz LinkedHashMap.Entry)

@ Pacerier poprawne jest to, że LinkedHashMap zachowuje tę samą kolejność podczas iteracji, jeśli element zostanie dodany ponownie, ale jest to tylko w przypadku trybu zamówienia wstawionego.

Oto co znalazłem w Javie dokumenty LinkedHashMap.Entry object

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

Ta metoda zajmuje się przenoszeniem ostatnio dostępnego elementu na koniec listy. Tak więc LinkedHashMap jest najlepszą strukturą danych do implementacji LRUCache.

 0
Author: Abhishek Gayakwad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-08 06:00:07

Kolejna myśl, a nawet prosta implementacja przy użyciu LinkedHashMap collection of Java.

LinkedHashMap dostarczona metoda removeEldestEntry i która może być nadpisana w sposób opisany w przykładzie. Domyślnie implementacja tej struktury kolekcji jest false. Jeśli jego prawda i rozmiar tej struktury wykracza poza początkową pojemność, starsze lub starsze elementy zostaną usunięte.

Możemy mieć pageno i zawartość strony w moim przypadku pageno jest liczbą całkowitą i pagecontent i zachowaj ciąg wartości numeru strony.

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

     }

}

Wynik wykonania powyższego kodu jest następujący:

 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
Author: Deepak Singhvi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-26 03:19:19

Android oferuje implementację LRU Cache. Kod jest czysty i prosty.

 -1
Author: eliocs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-18 09:07:11