आप जावा में LRU कैश कैसे लागू करेंगे?


169

कृपया EHCache या OSCache इत्यादि न कहें, इस प्रश्न के प्रयोजनों के लिए मान लें कि मैं अपने एसडीके का उपयोग करके स्वयं को लागू करना चाहता हूं (सीखकर)। यह देखते हुए कि कैश का उपयोग एक बहुस्तरीय वातावरण में किया जाएगा, आप कौन से डेटास्ट्रक्चर का उपयोग करेंगे? मैंने पहले से ही लिंक्डहैश मैप और कलेक्शंस # सिंक्रोनाइज़ का उपयोग करके एक को लागू किया है , लेकिन मैं उत्सुक हूं कि यदि कोई नया समवर्ती संग्रह बेहतर उम्मीदवार होगा।

अद्यतन: जब मैं यह डला हुआ पाया तो मैं येज के नवीनतम के माध्यम से पढ़ रहा था :

यदि आपको निरंतर-समय तक पहुँच की आवश्यकता है और प्रविष्टि क्रम को बनाए रखना चाहते हैं, तो आप लिंक्डहैशपॉइंट से बेहतर नहीं कर सकते, वास्तव में एक अद्वितीय संरचना है। एकमात्र तरीका यह संभवतः अधिक अद्भुत हो सकता है अगर कोई समवर्ती संस्करण था। लेकिन अफसोस।

इससे पहले कि मैं ऊपर उल्लेख किया LinkedHashMap+ Collections#synchronizedMapकार्यान्वयन के साथ मैं लगभग एक ही बात सोच रहा था । यह जानकर अच्छा लगा कि मैंने कुछ अनदेखी नहीं की।

अब तक के जवाबों के आधार पर, यह लगता है कि एक उच्च समवर्ती LRU के लिए मेरा सबसे अच्छा शर्त है कि कुछ इसी तर्क का उपयोग करके समवर्ती HashMap का विस्तार करना होगा LinkedHashMap



इसी तरह का सवाल यहाँ
मोइफेट

जवाबों:


102

मैं इन सुझावों के बहुत सारे की तरह है, लेकिन अब के लिए मुझे लगता है कि मैं के साथ रह सकते हैं LinkedHashMap+ Collections.synchronizedMap। तो मैं भविष्य में इस पर फिर से करते हैं, मैं शायद विस्तार पर काम करेंगे ConcurrentHashMapमें उसी तरह LinkedHashMapफैली हुई है HashMap

अपडेट करें:

अनुरोध से, यहाँ मेरे वर्तमान कार्यान्वयन का सार है।

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

15
लेकिन मैं विरासत के बजाय यहाँ encapsulation का उपयोग करना चाहूंगा। यह कुछ ऐसा है जो मैंने प्रभावी जावा से सीखा है।
कपिल डी

10
@KapilD यह एक समय हो गया है, लेकिन मैं LinkedHashMapLRU कार्यान्वयन बनाने के लिए स्पष्ट रूप से इस पद्धति का समर्थन करने के लिए जावाडॉक्स लगभग सकारात्मक हूं ।
हांक गे

7
@HankGay जावा का लिंक्डहैश मैप (तीसरे पैरामीटर = सच के साथ) LRU कैश नहीं है। ऐसा इसलिए है क्योंकि किसी प्रविष्टि को फिर से डालने से प्रविष्टियों के क्रम पर कोई असर नहीं पड़ता (एक वास्तविक LRU कैश पुनरावृत्ति क्रम के अंतिम-प्रविष्टि प्रविष्ट करेगा चाहे वह प्रविष्टि प्रारंभ में कैश में मौजूद हो)
Pacerier

2
@ स्पेसर मैं इस व्यवहार को बिल्कुल नहीं देखता। AccessOrder सक्षम नक्शे के साथ, सभी क्रियाएं सबसे हाल ही में उपयोग किए गए (ताज़ा) के रूप में एक प्रविष्टि बनाती हैं: प्रारंभिक सम्मिलन, मूल्य अद्यतन और मूल्य शिकायत। क्या मैं कुछ भूल रहा हूँ?
एसेलीजा

3
@ स्पेसर "किसी प्रविष्टि को फिर से डालना प्रविष्टियों के क्रम को प्रभावित नहीं करता है", यह गलत है। यदि आप LinkedHashMap के कार्यान्वयन में देखते हैं, तो "पुट" विधि के लिए, यह HashMap से कार्यान्वयन को विरासत में मिला है। और हाशपैप के जेवाडॉक कहते हैं "यदि मानचित्र में पहले कुंजी के लिए मैपिंग शामिल है, तो पुराने मूल्य को बदल दिया जाता है"। और यदि आप पुराने स्रोत को प्रतिस्थापित करते समय इसके स्रोत कोड की जांच करते हैं, तो यह रिकॉर्डअसेक विधि कहलाएगा, और लिंक्डहाशपैप के रेकॉर्ड विधि में, यह इस तरह दिखता है: यदि (lm.accessOrder) {lm.odCount ++; हटाना(); addBefore (lm.header);}
nybon


10

यह दो का दौर है।

पहला दौर वह था जिसके साथ मैं आया था, फिर मैंने अपने सिर में और अधिक घिरे हुए डोमेन के साथ टिप्पणियों को फिर से लिखा।

तो यहां एक यूनिट टेस्ट के साथ सबसे सरल संस्करण है जो दिखाता है कि यह कुछ अन्य संस्करणों के आधार पर काम करता है।

पहला गैर-समवर्ती संस्करण:

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


}

सच्चा ध्वज प्राप्त और पुट की पहुंच को ट्रैक करेगा। JavaDocs देखें। निर्माता को सही ध्वज के बिना निष्कासन को हटाए बस एक FIFO कैश लागू होगा (FIFO पर नीचे दिए गए नोट्स और हटाने के लिए देखें)।

यहाँ परीक्षण है कि यह साबित होता है कि यह 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 ();

    }

अब समवर्ती संस्करण के लिए ...

पैकेज 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 ();
    }


}

आप देख सकते हैं कि मैं पहले गैर-समवर्ती संस्करण को क्यों कवर करता हूं। उपर्युक्त प्रयास लॉक कंटेंट को कम करने के लिए कुछ धारियां बनाने का प्रयास करते हैं। तो हम इसे कुंजी को हैश करते हैं और फिर वास्तविक कैश को खोजने के लिए उस हैश को देखते हैं। यह आपकी कुंजी हैश एल्गोरिथ्म को कितनी अच्छी तरह से फैलाता है, इस पर निर्भर करता है कि त्रुटि की एक उचित मात्रा के भीतर एक सुझाव / मोटे अनुमान की सीमा आकार अधिक बनाता है।

यहां यह दिखाने के लिए परीक्षण किया गया है कि समवर्ती संस्करण शायद काम करता है। :) (आग के नीचे परीक्षण वास्तविक तरीका होगा)।

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



    }

}

यह आखिरी पोस्ट है .. पहली पोस्ट मैंने डिलीट कर दी थी क्योंकि यह एलएफयू थी न कि एलआरयू कैश।

मैंने सोचा कि मैं इसे दूसरा रास्ता दूंगा। मैं मानक JDK w / o बहुत अधिक कार्यान्वयन का उपयोग करके LRU कैश के सबसे सरल संस्करण के साथ आने की कोशिश कर रहा था।

यहां वह है जो मैंने जुटाया। मेरा पहला प्रयास एक आपदा का था क्योंकि मैंने और एलआरयू के बजाय एलएफयू को लागू किया था, और फिर मैंने इसमें फीफो और एलआरयू का समर्थन जोड़ा ... और फिर मुझे एहसास हुआ कि यह एक राक्षस बन रहा है। फिर मैंने अपने दोस्त जॉन से बात करना शुरू कर दिया, जो मुश्किल से दिलचस्पी ले रहा था, और फिर मैंने गहरी लंबाई में बताया कि मैंने एक एलएफयू, एलआरयू और एफआईएफओ को कैसे लागू किया और आप इसे एक सरल ईनम आर्ग के साथ कैसे स्विच कर सकते हैं, और तब मुझे एहसास हुआ कि सभी वास्तव में चाहते थे एक साधारण LRU था। तो मेरे से पहले की पोस्ट को अनदेखा करें, और मुझे बताएं कि क्या आप एक LRU / LFU / FIFO कैश देखना चाहते हैं जो कि एनम के माध्यम से switchable है ... नहीं? ठीक है .. यहाँ वह जाओ।

केवल JDK का उपयोग करके सबसे सरल संभव LRU। मैंने एक समवर्ती संस्करण और एक गैर-समवर्ती संस्करण दोनों को लागू किया।

मैंने एक सामान्य इंटरफ़ेस बनाया (यह अतिसूक्ष्मवाद है इसलिए कुछ विशेषताओं को याद नहीं किया जा सकता है जो आप चाहेंगे लेकिन यह मेरे उपयोग के मामलों के लिए काम करता है, लेकिन यदि आप सुविधा XYZ देखना चाहते हैं तो मुझे बताएं ... मैं कोड लिखना चाहता हूं।) ।

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

आपको आश्चर्य हो सकता है कि क्या मिल रहा है है। मैं इसे परीक्षण के लिए उपयोग करता हूं। getSilent किसी आइटम का LRU स्कोर नहीं बदलता है।

पहले गैर-समवर्ती एक ...।

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

queue.removeFirstOccurrence यदि आप किसी बड़े कैश है एक संभावित महंगा ऑपरेशन है। एक लिंक्डलिस्ट को एक उदाहरण के रूप में ले सकता है और एक ऑपरेशन को हटाने और अधिक सुसंगत बनाने के लिए तत्व से नोड तक रिवर्स लुकअप हैश मैप को जोड़ सकता है। मैंने भी शुरुआत की, लेकिन तब मुझे महसूस हुआ कि मुझे इसकी आवश्यकता नहीं है। लेकिन हो सकता है...

जब डाल कहा जाता है, कुंजी कतार में जोड़ा जाता है। कब मिलेगा? कहा जाता है, कुंजी हटा दिया जाता है और कतार में शीर्ष पर फिर से जोड़ दिया।

यदि आपका कैश छोटा है और किसी वस्तु का निर्माण महंगा है तो यह एक अच्छा कैश होना चाहिए। यदि आपका कैश वास्तव में बड़ा है, तो रैखिक खोज एक बोतल गर्दन हो सकती है, खासकर यदि आपके पास कैश के गर्म क्षेत्र नहीं हैं। गर्म स्थान जितना अधिक तीव्र होता है, गर्म वस्तुओं के रूप में उतनी ही तेजी से रैखिक खोज हमेशा रैखिक खोज के शीर्ष पर होती है। वैसे भी ... इसके लिए तेजी से जाने के लिए क्या आवश्यक है एक और लिंक्डलिस्ट लिखिए जिसमें एक हटाने का ऑपरेशन होता है जिसमें हटाने के लिए नोड लुकअप करने के लिए उल्टा तत्व होता है, फिर एक हैश मैप से एक कुंजी को हटाने के रूप में हटाने के रूप में तेजी से होगा।

यदि आपके पास 1,000 वस्तुओं के तहत कैश है, तो यह ठीक काम करना चाहिए।

यहां कार्रवाई में अपने संचालन को दिखाने के लिए एक सरल परीक्षण है।

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

    }
}

अंतिम LRU कैश एकल थ्रेडेड था, और कृपया इसे किसी भी सिंक्रनाइज़ चीज़ में लपेटें नहीं ...।

यहाँ एक समवर्ती संस्करण में एक छुरा है।

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

मुख्य अंतर हैशपॉप के बजाय कॉनकंट्रैशहैश का उपयोग है, और लॉक का उपयोग (मुझे सिंक्रनाइज़ के साथ दूर हो सकता है, लेकिन ...)।

मैंने इसे आग के तहत परीक्षण नहीं किया है, लेकिन यह एक साधारण LRU कैश की तरह लगता है जो 80% उपयोग के मामलों में काम कर सकता है जहां आपको एक साधारण LRU मानचित्र की आवश्यकता होती है।

मैं प्रतिक्रिया का स्वागत करता हूं, सिवाय इसके कि आप लाइब्रेरी ए, बी या सी का उपयोग क्यों नहीं करते हैं। मेरे द्वारा हमेशा लाइब्रेरी का उपयोग न करने का कारण यह है क्योंकि मैं हमेशा नहीं चाहता कि हर युद्ध की फाइल 80 एमबी की हो, और मैं लाइब्रेरी लिखता हूं, इसलिए मैं जगह में अच्छे पर्याप्त समाधान के साथ लिबास को प्लग-इन करने में सक्षम हूं और कोई भी प्लग कर सकता है एक और कैश प्रदाता में अगर वे पसंद करते हैं। :) मैं कभी नहीं जानता कि कब किसी को अमरूद या ईचेचे या कुछ और चाहिए जो मैं उन्हें शामिल नहीं करना चाहता, लेकिन अगर मैं कैशिंग प्लग-इन करूं, तो मैं उन्हें बाहर नहीं करूंगा।

निर्भरता कम करने का अपना प्रतिफल होता है। मैं इसे और भी सरल या तेज या दोनों बनाने के लिए कुछ प्रतिक्रिया प्राप्त करना पसंद करता हूं।

साथ ही अगर कोई जाने के लिए तैयार है ...।

ठीक है .. मुझे पता है कि आप क्या सोच रहे हैं ... वह लिंक्डहैशपाइप से हटाने की सबसे बड़ी प्रविष्टि का उपयोग क्यों नहीं करता है, और अच्छी तरह से मुझे करना चाहिए लेकिन .... लेकिन .. लेकिन .. यह एक फीफो नहीं एक LRU होगा और हम थे एक 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 ();

तो यहाँ removeEldestEntry का उपयोग करके एक त्वरित और गंदा FIFO कैश है।

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

एफआईएफओ तेज हैं। आसपास कोई खोज नहीं। आप एक LRU के सामने एक FIFO लगा सकते हैं और यह सबसे गर्म प्रविष्टियों को काफी अच्छी तरह से हैंडल करेगा। एक बेहतर एलआरयू को उस उल्टे तत्व को नोड सुविधा की आवश्यकता होने जा रही है।

वैसे भी ... अब जब मैंने कुछ कोड लिखा है, तो मुझे अन्य उत्तरों के माध्यम से जाने दें और देखें कि मैंने क्या याद किया ... पहली बार मैंने उन्हें स्कैन किया।


9

LinkedHashMapO (1) है, लेकिन सिंक्रनाइज़ेशन की आवश्यकता है। वहाँ पहिया को सुदृढ़ करने की आवश्यकता नहीं है।

संगामिति बढ़ाने के लिए 2 विकल्प:

1. कई बनाएँ LinkedHashMap, और उनमें हैश: उदाहरण LinkedHashMap[4], index 0, 1, 2, 3:। कुंजी पर key%4 (या binary ORपर [key, 3]) लेने के लिए जो एक पुट / पाने / हटाने के लिए कौन सा नक्शा है।

2. आप एक 'लगभग' LRU का विस्तार कर सकते हैं ConcurrentHashMap, और इसके अंदर के प्रत्येक क्षेत्र में संरचना की तरह एक जुड़ा हुआ हैश मानचित्र हो सकता है। लॉकिंग एक से अधिक दानेदार होता है जो LinkedHashMapकि सिंक्रनाइज़ किया जाता है। एक पर putया putIfAbsentकेवल सिर और सूची की पूंछ पर एक ताला (प्रति क्षेत्र) की जरूरत है। हटाने या प्राप्त करने पर पूरे क्षेत्र को लॉक करने की आवश्यकता होती है। मैं उत्सुक हूँ अगर परमाणु किसी प्रकार की लिंक्ड सूची यहाँ मदद कर सकते हैं - शायद सूची के प्रमुख के लिए। शायद अधिक के लिए।

संरचना कुल आदेश नहीं, बल्कि प्रति क्षेत्र केवल आदेश रखेगी। जब तक प्रविष्टियों की संख्या क्षेत्रों की संख्या से बहुत बड़ी है, यह अधिकांश कैश के लिए पर्याप्त है। प्रत्येक क्षेत्र के पास अपनी प्रविष्टि की संख्या होगी, इसका उपयोग निष्कासन ट्रिगर के लिए वैश्विक गणना के बजाय किया जाएगा। क्षेत्रों की डिफ़ॉल्ट संख्या ConcurrentHashMap16 है, जो आज अधिकांश सर्वरों के लिए बहुत है।

  1. मध्यम सुगमता के तहत लिखना और तेज करना आसान होगा।

  2. लिखना अधिक कठिन होगा लेकिन बहुत उच्च संगति पर बेहतर होगा। यह सामान्य पहुंच के लिए धीमा होगा (ठीक उसी तरह जैसे कि जहां कोई सुगमता नहीं है, वहां ConcurrentHashMapसे धीमी है HashMap)


8

दो खुले स्रोत कार्यान्वयन हैं।

Apache Solr में ConcurrentLRUCache है: https://lucene.apache.org/solr/3_6_1/org/apache/solr/util/ConcurrentLRUCache.html

एक समवर्ती LinkedHashMap के लिए एक खुला स्रोत परियोजना है: http://code.google.com/p/concurrentlinkedhashmap/


2
सोलर का समाधान वास्तव में LRU नहीं है, लेकिन ConcurrentLinkedHashMapदिलचस्प है। यह दावा किया गया है MapMakerकि अमरूद में से लुढ़का हुआ है , लेकिन मैंने इसे डॉक्स में जगह नहीं दी। किसी भी विचार है कि प्रयास के साथ क्या हो रहा है?
हांक गे

3
एक सरलीकृत संस्करण एकीकृत किया गया था, लेकिन परीक्षण पूर्ण नहीं हुए हैं इसलिए यह अभी तक सार्वजनिक नहीं है। मुझे गहन एकीकरण करने में बहुत सारी समस्याएं थीं, लेकिन मैं इसे खत्म करने की उम्मीद करता हूं क्योंकि कुछ अच्छे एल्गोरिदम गुण हैं। एक निष्कासन (क्षमता, समाप्ति, जीसी) को सुनने की क्षमता को जोड़ा गया और यह सीएलएचएम के दृष्टिकोण (श्रोता कतार) पर आधारित है। मैं "भारित मूल्यों" के विचार को भी योगदान देना चाहूंगा, क्योंकि यह संग्रह संग्रह करते समय उपयोगी है। दुर्भाग्य से अन्य प्रतिबद्धताओं के कारण मुझे भी अमरूद के पात्र (और मैंने केविन / चार्ल्स का वादा किया) को समर्पित करने के लिए बहुत समय दिया गया था।
बेन मैंस

3
अद्यतन: एकीकरण पूरा हो गया था और अमरूद r08 में सार्वजनिक था। यह #maximumSize () सेटिंग के माध्यम से।
बेन मैंस

7

मैं java.util.concurrent.PyerityBlockingQueue का उपयोग करने पर विचार करूंगा , जिसमें प्रत्येक तत्व में "नंबरऑफ़यूज़" काउंटर द्वारा निर्धारित प्राथमिकता होगी। मैं अपने सभी सिंक्रोनाइज़ेशन को सही करने के लिए बहुत सावधान हूं, क्योंकि "नंबरऑफयूज" काउंटर का अर्थ है कि तत्व अपरिवर्तनीय नहीं हो सकता है।

तत्व ऑब्जेक्ट कैश में ऑब्जेक्ट के लिए एक आवरण होगा:

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

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

    ... etc.
}

क्या आपका मतलब अपरिवर्तनीय नहीं है?
शीस्टमीयर

2
ध्यान दें कि यदि आप स्टीव मूसोड द्वारा उल्लिखित प्राथमिकताब्लॉकिंगक्यू संस्करण करने का प्रयास करते हैं, तो आपको तत्व को अपरिवर्तनीय बनाना चाहिए, क्योंकि कतार में तत्व को संशोधित करते समय कोई प्रभाव नहीं पड़ेगा, आपको तत्व को हटाने और इसे फिर से जोड़ने की आवश्यकता होगी। इसे फिर से प्राथमिकता दें।
जेम्स

जेम्स नीचे एक त्रुटि है जो मैंने बनाया है। जो मैं सबूत के रूप में प्रदान करता हूं कि कैसे विश्वसनीय, मजबूत कैश लिखना मुश्किल है।
स्टीव मैक्लोड

6

उम्मीद है की यह मदद करेगा ।

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

1
अच्छा उदाहरण! क्या आप टिप्पणी कर सकते हैं कि अधिकतम 4 * 3 की क्षमता निर्धारित करने की आवश्यकता क्यों है?
अकवेल

1
@Akvel इसे प्रारंभिक क्षमता कहा जाता है, कोई भी [पूर्णांक] मान हो सकता है जबकि 0.75f ​​डिफ़ॉल्ट लोड कारक है, आशा है कि यह लिंक मदद करता है: ashishsharma.me/2011/09/custom-lru-cache-java.html
muringing

5

LRU Cache को एक समवर्तीLinkedue और एक समवर्ती HashMap का उपयोग करके कार्यान्वित किया जा सकता है जो कि मल्टीथ्रेडिंग परिदृश्य में भी उपयोग किया जा सकता है। कतार का प्रमुख वह तत्व है जो कतार में सबसे लंबे समय तक रहा है। कतार की पूंछ वह तत्व है जो कतार में सबसे कम समय पर होता है। जब कोई तत्व मानचित्र में मौजूद होता है, तो हम इसे लिंक्डक्यू से हटा सकते हैं और इसे पूंछ में डाल सकते हैं।

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

}

यह थ्रेडसेफ़ नहीं है । उदाहरण के लिए आप आसानी से समवर्ती कॉल करके अधिकतम LRU आकार को पार कर सकते हैं put
dpeacock

कृपया इसे ठीक करें। सबसे पहले यह लाइन map.containsKey (कुंजी) पर संकलित नहीं करता है। दूसरी बात यह है कि आपको यह देखना चाहिए कि क्या वास्तव में कुंजी को हटा दिया गया था अन्यथा मानचित्र और कतार सिंक से बाहर हो जाते हैं और "queue.size (>> = size") हमेशा सच हो जाता है। मैं अपने संस्करण को इस निश्चित के बाद पोस्ट करूंगा क्योंकि मुझे इन दो संग्रहों का उपयोग करने का आपका विचार पसंद आया।
अलेक्जेंडर लेक

3

यहां एलआरयू के लिए मेरा कार्यान्वयन है। मैंने प्रायोरिटी क्यू का उपयोग किया है, जो मूल रूप से एफआईएफओ के रूप में काम करता है, न कि थ्रेडसेफ़। पेज टाइम क्रिएशन के आधार पर और कम से कम हाल ही में उपयोग किए गए समय के लिए पेजों के क्रम के आधार पर इस्तेमाल किया गया कम्पैक्टर।

विचार के लिए पृष्ठ: 2, 1, 0, 2, 8, 2, 4

कैश में जोड़ा गया पेज है: 2
पेज को कैश में जोड़ा गया है: 1
पेज को कैश में जोड़ा गया है: 0
पेज: 2 कैश में पहले से ही मौजूद है। अंतिम समय तक अपडेट किया गया
पेज फाल्ट, पेज: 1,
पेज के साथ बदला गया: 8 पेज कैश में जोड़ा गया है: 8
पेज: 2 कैश में पहले से ही मौजूद है। अंतिम बार देखा गया अपडेट किया गया
पृष्ठ दोष, पेज: 0,
पेज के साथ बदला गया: 4 पेज कैश में जोड़ा गया है: 4

आउटपुट

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

यहाँ कोड दर्ज करें

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

2

यहां मेरा परीक्षण सबसे अच्छा प्रदर्शन करने वाला समवर्ती LRU कैश कार्यान्वयन है जो बिना किसी ब्लॉक किए हुए है:

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

}


1
@zoltan boda .... आपने एक स्थिति को संभाला नहीं है .. यदि एक ही वस्तु को कई बार इस्तेमाल किया जाए तो क्या होगा? इस मामले में हमें एक ही वस्तु के लिए कई प्रविष्टियाँ नहीं

5
चेतावनी: यह LRU कैश नहीं है। LRU कैश में, आप कम से कम हाल ही में एक्सेस की गई वस्तुओं को फेंक देते हैं। यह कम से कम हाल ही में लिखी गई वस्तुओं को फेंक देता है। यह कतार (क्रेम) संचालन करने के लिए एक रैखिक स्कैन भी है।
डेव एल।

इसके अलावा समवर्तीलिखित क्यू # आकार () एक निरंतर समय ऑपरेशन नहीं है।
नैटस

3
आपकी पुट विधि सुरक्षित नहीं दिखती है - इसमें कुछ चेक-तब-एक्ट स्टेटमेंट हैं जो कई थ्रेड्स के साथ टूटेंगे।
अक्शी

2

यह मेरे द्वारा उपयोग किया जाने वाला LRU कैश है, जो LinkedHashMap को इनकैप्सुलेट करता है और रसदार स्पॉट की रखवाली करने वाले एक साधारण सिंक्रोनाइज़ लॉक के साथ कंसीडर को हैंडल करता है। यह "तत्वों" को छूता है क्योंकि उनका उपयोग किया जाता है ताकि वे फिर से "सबसे ताज़ी" तत्व बनें, ताकि यह वास्तव में LRU हो। मुझे अपने तत्वों की न्यूनतम जीवनकाल की आवश्यकता भी थी, जिसे आप "अधिकतम निष्क्रिय समय" के रूप में भी सोच सकते हैं, तो आप बेदखली के लिए तैयार हैं।

हालाँकि, मैं हांक के निष्कर्ष और स्वीकृत उत्तर से सहमत हूँ - अगर मैं इसे आज फिर से शुरू कर रहा था, तो मैं अमरूद की जाँच करूँगा 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

एक कैश के लिए आप आम तौर पर प्रॉक्सी ऑब्जेक्ट, (एक यूआरएल, स्ट्रिंग ....) के माध्यम से डेटा के कुछ टुकड़े को देख रहे होंगे, इसलिए इंटरफ़ेस-वार आप एक नक्शा चाहते हैं। लेकिन चीजों को किक करने के लिए आपको संरचना जैसी कतार चाहिए। आंतरिक रूप से मैं दो डेटा संरचना, एक प्राथमिकता-कतार और एक हाशपैप बनाए रखूंगा। एक कार्यान्वयन है जो ओ (1) समय में सब कुछ करने में सक्षम होना चाहिए।

यहाँ एक वर्ग है जिसे मैंने बहुत जल्दी कोड़ा है:

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

यहां देखिए यह कैसे काम करता है। कुंजी को लिंक की गई सूची में सबसे पुरानी कुंजी के साथ सूची के सामने संग्रहीत किया जाता है (नई कुंजी पीछे जाती है) इसलिए जब आपको किसी चीज को 'बेदखल' करने की आवश्यकता होती है तो आप इसे कतार के सामने से हटा देते हैं और फिर कुंजी का उपयोग करते हैं मानचित्र से मान निकालें। जब कोई आइटम संदर्भित हो जाता है, तो आप मानचित्र से ValueHolder को पकड़ लेते हैं और फिर कतार में अपने वर्तमान स्थान से कुंजी को हटाने के लिए कतारबद्ध चर का उपयोग करते हैं और फिर इसे कतार के पीछे रख देते हैं (अब इसका सबसे हाल ही में उपयोग किया गया)। चीजें जोड़ना बहुत समान है।

मुझे यकीन है कि यहाँ त्रुटियों का एक टन है और मैंने किसी भी सिंक्रनाइज़ेशन को लागू नहीं किया है। लेकिन यह वर्ग कैश में ओ (1) जोड़ देगा, ओ (1) पुरानी वस्तुओं को हटाने, और कैश वस्तुओं की ओ (1) पुनर्प्राप्ति। यहां तक ​​कि एक तुच्छ सिंक्रनाइज़ेशन (बस हर सार्वजनिक विधि को सिंक्रनाइज़ करें) रन टाइम के कारण अभी भी थोड़ा लॉक विवाद होगा। अगर किसी के पास कोई चालाक तुल्यकालन चाल है, तो मुझे बहुत दिलचस्पी होगी। इसके अलावा, मुझे यकीन है कि कुछ अतिरिक्त अनुकूलन हैं जिन्हें आप मानचित्र के संबंध में अधिकतम चर का उपयोग करके लागू कर सकते हैं।


विस्तार के स्तर के लिए धन्यवाद, लेकिन यह LinkedHashMap+ Collections.synchronizedMap()कार्यान्वयन पर एक जीत कहां प्रदान करता है?
हांक गे

प्रदर्शन, मुझे यकीन है कि पता नहीं है, लेकिन मुझे नहीं लगता कि LinkedHashMap में O (1) प्रविष्टि है (शायद इसका O (लॉग (n))), वास्तव में आप मेरे कार्यान्वयन में मानचित्र इंटरफ़ेस को पूरा करने के लिए कुछ तरीके जोड़ सकते हैं और फिर संगामिति जोड़ने के लिए Collections.synchronizedMap का उपयोग करें।
ल्यूक

लिंक विधि में ऊपर लिंक्डलिस्ट क्लास में एक और ब्लॉक में एक कोड होता है, अर्थात यदि (tail.prev == null) {tail.prev = head; head.next = tail; } इस कोड को कब अमल में लाया जाएगा? मैंने कुछ सूखे रन बनाए और मुझे लगता है कि इस पर कभी अमल नहीं किया जाएगा और इसे हटा दिया जाना चाहिए।
दीपेश

1

समवर्तीSkipListMap पर एक नज़र है । यह आपको किसी तत्व को परीक्षण और हटाने के लिए लॉग (n) समय देना चाहिए, यदि यह पहले से ही कैश में सम्‍मिलित है, और इसे फिर से जोड़ने के लिए निरंतर समय है।

आपको LRU ऑर्डर के लिए बाध्य करने के लिए कुछ काउंटर आदि और रैपर तत्व की आवश्यकता होगी और यह सुनिश्चित करना होगा कि कैश भरा होने पर हाल के सामान को छोड़ दिया जाए।


चाहेंगे ConcurrentSkipListMapपर कुछ आसानी से कार्यान्वयन लाभ प्रदान ConcurrentHashMap, या यह केवल रोग के मामलों से बचने का एक मामला है?
हांक गे

यह समवर्ती चीजों को सरल बना देगा, जैसा कि समवर्ती SsLListMap तत्वों को आदेशित करता है, जो आपको प्रबंधित करने की अनुमति देगा कि किस क्रम में उपयोग किया गया था। ConcurrentHashMap ऐसा नहीं करता है, इसलिए आपको मूल रूप से एक तत्व के अंतिम को अपडेट करने के लिए संपूर्ण कैश सामग्री पर चलना होगा। प्रयुक्त काउंटर 'या जो भी
madlep

इसलिए ConcurrentSkipListMapकार्यान्वयन के साथ , मैं उस Mapइंटरफ़ेस का एक नया कार्यान्वयन ConcurrentSkipListMapबनाऊंगा जो किसी प्रकार के रैपिंग को दर्शाता और निष्पादित करता है ताकि मनमाने कुंजी प्रकार एक प्रकार में लिपटे रहें जो अंतिम पहुंच के आधार पर आसानी से सॉर्ट किए जाते हैं?
हांक गे

1

यहाँ मेरा संक्षिप्त कार्यान्वयन है, कृपया इसकी आलोचना करें या सुधार करें!

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
यह LRU कैश नहीं है, बस FIFO कैश है।
lslab

1

यहाँ इस समस्या पर मेरा अपना कार्यान्वयन है

simplelrucache थ्रेडसेफ़ प्रदान करता है, टीटीएल समर्थन के साथ बहुत सरल, गैर-वितरित एलआरयू कैशिंग। यह दो कार्यान्वयन प्रदान करता है:

  • समवर्ती पर आधारित समवर्तीLashHashMap
  • LinkedHashMap के आधार पर सिंक्रनाइज़ किया गया

आप इसे यहां देख सकते हैं: http://code.google.com/p/simplelrucache/


1

प्राप्त करने का सबसे अच्छा तरीका एक लिंक्डहैश मैप का उपयोग करना है जो तत्वों के सम्मिलन क्रम को बनाए रखता है। निम्नलिखित एक नमूना कोड है:

public class Solution {

Map<Integer,Integer> cache;
int capacity;
public Solution(int capacity) {
    this.cache = new LinkedHashMap<Integer,Integer>(capacity); 
    this.capacity = capacity;

}

// This function returns false if key is not 
// present in cache. Else it moves the key to 
// front by first removing it and then adding 
// it, and returns true. 

public int get(int key) {
if (!cache.containsKey(key)) 
        return -1; 
    int value = cache.get(key);
    cache.remove(key); 
    cache.put(key,value); 
    return cache.get(key); 

}

public void set(int key, int value) {

    // If already present, then  
    // remove it first we are going to add later 
       if(cache.containsKey(key)){
        cache.remove(key);
    }
     // If cache size is full, remove the least 
    // recently used. 
    else if (cache.size() == capacity) { 
        Iterator<Integer> iterator = cache.keySet().iterator();
        cache.remove(iterator.next()); 
    }
        cache.put(key,value);
}

}


0

मैं जावा कोड का उपयोग करके एक बेहतर LRU कैश की तलाश कर रहा हूं। क्या आपके लिए अपने जावा LRU कैश कोड का उपयोग करना LinkedHashMapऔर साझा करना संभव है Collections#synchronizedMap? वर्तमान में मैं उपयोग कर रहा हूं LRUMap implements Mapऔर कोड ठीक काम कर रहा है, लेकिन मैं ArrayIndexOutofBoundExceptionनीचे की विधि पर 500 उपयोगकर्ताओं का उपयोग करके लोड परीक्षण कर रहा हूं । विधि हाल के ऑब्जेक्ट को कतार के सामने ले जाती है।

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)और put(Object key, Object value)विधि उपरोक्त विधि को कॉल करती है moveToFront


0

हंक द्वारा दिए गए उत्तर पर टिप्पणी जोड़ना चाहते थे लेकिन कुछ मैं कैसे सक्षम नहीं हूं - कृपया इसे टिप्पणी के रूप में मानें

LinkedHashMap अपने निर्माता में पारित पैरामीटर के आधार पर पहुंच के क्रम को बनाए रखता है और यह ऑर्डर बनाए रखने के लिए दोगुनी पंक्तिबद्ध सूची रखता है (देखें LinkedHashMap.Entry)

@Pacerier यह सही है कि LinkedHashMap एक ही क्रम रखता है जबकि पुनरावृत्ति यदि तत्व को फिर से जोड़ा जाता है, लेकिन यह केवल प्रविष्टि क्रम मोड के मामले में है।

यह मुझे 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);
        }
    }

यह विधि सूची के अंत में हाल ही में एक्सेस किए गए तत्व को स्थानांतरित करने का ख्याल रखती है। इसलिए सभी LinkedHashMap में LRUCache को लागू करने के लिए सबसे अच्छी डेटा संरचना है।


0

एक और विचार और यहां तक ​​कि जावा के लिंक्डहाशपैप संग्रह का उपयोग करके एक सरल कार्यान्वयन।

LinkedHashMap ने विधि निष्कासन प्रदान किया हैएडस्टेंट्री और जिसे उदाहरण में वर्णित तरीके से ओवरराइड किया जा सकता है। डिफ़ॉल्ट रूप से इस संग्रह संरचना का कार्यान्वयन गलत है। यदि इस संरचना का वास्तविक और आकार प्रारंभिक क्षमता से आगे जाता है तो सबसे पुराने या पुराने तत्वों को हटा दिया जाएगा।

हमारे पास पेजेनो हो सकता है और मेरे मामले में पेज कंटेंट पेजेनो पूर्णांक है और पेज कॉन्टेंट मैंने पृष्ठ संख्या मान स्ट्रिंग रखा है।

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

     }

}

उपरोक्त कोड निष्पादन का परिणाम निम्नानुसार है:

 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 

यह एक फीफो है। उन्होंने LRU के लिए कहा।
रिकहिग

यह इस परीक्षण में विफल रहता है ... cache.get (2); cache.get (3); cache.put (6, 6); cache.put (7, 7); ok | = cache.size () == 4 || die ("आकार" + cache.size ()); ok | = cache.getSilent (2) == 2 || मरो (); ok | = cache.getSilent (3) == 3 || मरो (); ok | = cache.getSilent (4) == null || मरो (); ok | = cache.getSilent (5) == null || मरो ();
रिकहिग

0

@Sanjanab अवधारणा (लेकिन फिक्स के बाद) के बाद मैंने LRUCache के अपने संस्करण को उपलब्ध कराया, जो कि उपभोक्ता को जरूरत पड़ने पर हटाए गए सामान के साथ कुछ करने की अनुमति देता है।

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.