web-dev-qa-db-ja.com

JavaでLRUキャッシュをどのように実装しますか?

EHCacheやOSCacheなどは言わないでください。この質問の目的のために、SDKのみを使用して独自に実装することを想定します(実行して学習します)。キャッシュがマルチスレッド環境で使用される場合、どのデータ構造を使用しますか? LinkedHashMap および Collections#synchronizedMap を使用して既に実装していますが、新しい同時コレクションのいずれかがより良い候補になるかどうか興味があります。

更新:このナゲットを見つけたとき、私はただ読みました Yeggeの最新

一定時間のアクセスが必要で、挿入順序を維持したい場合は、本当に素晴らしいデータ構造であるLinkedHashMapを超えることはできません。おそらくもっと素晴らしい可能性がある唯一の方法は、並行バージョンがあった場合です。しかし悲しいかな。

上記のLinkedHashMap + Collections#synchronizedMap実装を使用する前に、私はほぼ同じことを考えていました。何かを見落としていなかったことを知ってうれしいです。

これまでの回答に基づいて、高度に同時実行されるLRUに対する最善の策は、LinkedHashMapが使用するものと同じロジックを使用して ConcurrentHashMap を拡張することです。

163
Hank Gay

今日もこれを最初からやり直す場合、Guavaの CacheBuilder を使用します。

16
Hank Gay

私はこれらの提案がたくさん好きですが、今のところはLinkedHashMap + Collections.synchronizedMapに固執すると思います。将来これを再検討する場合は、おそらくConcurrentHashMapLinkedHashMapHashMapを拡張するのと同じ方法で拡張することに取り組むでしょう。

更新:

要求に応じて、現在の実装の要点を以下に示します。

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));
100
Hank Gay
19
Joe

LinkedHashMapはO(1)ですが、同期が必要です。そこで車輪を再発明する必要はありません。

並行性を高めるための2つのオプション:

1.複数のLinkedHashMapを作成し、それらにハッシュします。例:LinkedHashMap[4], index 0, 1, 2, 3。キーでkey%4(またはbinary OR[key, 3])を実行して、put/get/removeを実行するマップを選択します。

2. ConcurrentHashMapを拡張し、その中の各領域に構造のようなリンクされたハッシュマップを持つことにより、「ほぼ」LRUを実行できます。ロックは、同期されるLinkedHashMapよりも細かく発生します。 putまたはputIfAbsentでは、リストの先頭と末尾のロックのみが必要です(領域ごと)。削除または取得時には、領域全体をロックする必要があります。ある種のアトミックリンクリストがここで役立つかどうかは、興味があります。たぶんもっと。

この構造では、全体の順序は保持されず、地域ごとの順序のみが保持されます。エントリの数がリージョンの数よりはるかに多い限り、これはほとんどのキャッシュに十分です。各リージョンには独自のエントリカウントが必要です。エビクショントリガーのグローバルカウントではなく、これが使用されます。 ConcurrentHashMapのデフォルトのリージョン数は16です。これは、今日のほとんどのサーバーで十分です。

  1. 中程度の並行性のもとで書くのがより簡単になり、より速くなります。

  2. 記述するのはより困難ですが、非常に高い同時実行性でははるかに優れた拡張性が得られます。通常のアクセスの場合は遅くなります(同時実行性がないConcurrentHashMapHashMapよりも遅くなります)

9
Scott

2つのオープンソース実装があります。

Apache SolrにはConcurrentLRUCacheがあります: https://lucene.Apache.org/solr/3_6_1/org/Apache/solr/util/ConcurrentLRUCache.html

ConcurrentLinkedHashMapにはオープンソースプロジェクトがあります。 http://code.google.com/p/concurrentlinkedhashmap/

8
Ron

Java.util.concurrent.PriorityBlockingQueue を使用することを検討します。各要素の「numberOfUses」カウンターによって優先順位が決定されます。 「numberOfUses」カウンターとして、すべての同期を正しく取得するために、非常に、非常に慎重に要素は不変にできないことを意味します。

要素オブジェクトは、キャッシュ内のオブジェクトのラッパーになります。

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

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

    ... etc.
}
7
Steve McLeod

お役に立てれば 。

import Java.util.*;
public class Lru {

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

        private static final long serialVersionUID = -3588047435434569014L;

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

LRUキャッシュは、マルチスレッドシナリオでも使用できるConcurrentLinkedQueueとConcurrentHashMapを使用して実装できます。キューの先頭は、キューに最も長く存在していた要素です。キューの末尾は、キューに最短時間であった要素です。マップに要素が存在する場合、LinkedQueueからその要素を削除して、末尾に挿入できます。

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

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

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

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

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

}
5
sanjanab

LRUの実装を次に示します。 PriorityQueueを使用しましたが、これは基本的にFIFOとして機能し、スレッドセーフではありません。ページ時間の作成に基づいて、使用されたコンパレータは、最も最近使用されていない時間のページの順序付けを実行します。

検討対象ページ:2、1、0、2、8、2、4

キャッシュに追加されるページは次のとおりです:2
キャッシュに追加されたページ:1
キャッシュに追加されたページ:0
ページ:2はすでにキャッシュに存在します。最終更新日時
ページフォールト、ページ:1、ページに置き換え:8
キャッシュに追加されたページ:8
ページ:2はすでにキャッシュに存在します。最終更新日時
ページ違反、ページ:0、ページに置き換え:4
キャッシュに追加されたページ:4

出力

LRUCacheページ
-------------
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;
    }
}
3
Deepak Singhvi

キャッシュについては、通常、プロキシオブジェクト(URL、文字列...)を介してデータの一部を検索するため、インターフェイス的にはマップが必要になります。しかし、物事を追い出すには、構造のようなキューが必要です。内部的には、Priority-QueueとHashMapの2つのデータ構造を維持します。 O(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を取得し、queuelocation変数を使用してキュー内の現在の場所からキーを削除し、キューの後ろに配置します(最近使用したもの)。物事を追加することはほとんど同じです。

ここには大量のエラーがあると確信しており、同期を実装していません。ただし、このクラスはO(1)をキャッシュに追加し、O(1)古いアイテムを削除し、O(1)キャッシュアイテムを取得します。 。些細な同期(すべてのパブリックメソッドを同期するだけ)でも、実行時のロック競合はほとんどありません。誰かが巧妙な同期のトリックを持っているなら、私は非常に興味があるでしょう。また、マップに関してmaxsize変数を使用して実装できる最適化がいくつかあると確信しています。

2
luke

これは私が使用する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
broc.seib

以下は、同期ブロックなしでテストした最高のパフォーマンスの同時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);
}

}

2
Zoltan Boda

ここに私の短い実装があります、それを批判または改善してください!

package util.collection;

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

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

public class ConcurrentLRUCache<Key, Value> {

private final int maxSize;
private int currentSize = 0;

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

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

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

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

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

この問題に対する私自身の実装は次のとおりです

simplelrucacheは、TTLをサポートする、スレッドセーフで非常にシンプルな非分散LRUキャッシングを提供します。次の2つの実装を提供します。

  • ConcurrentLinkedHashMapに基づく並行
  • LinkedHashMapに基づいて同期

こちらで見つけることができます: http://code.google.com/p/simplelrucache/

1
Daimon

ConcurrentSkipListMap をご覧ください。既にキャッシュに含まれている要素をテストおよび削除するためのlog(n)時間と、再追加す​​るための一定の時間を提供する必要があります。

LRU順序の順序付けを強制し、キャッシュがいっぱいになったときに最新のものが破棄されるようにするには、カウンターなどとラッパー要素が必要です。

1
madlep

@sanjanabの概念に従って(ただし修正後)、必要に応じて削除されたアイテムで何かを実行できるConsumerも提供する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);
    }
}
0
Aleksander Lech

ハンクによって与えられた答えにコメントを追加したかったが、どうにかできない-コメントとして扱ってください

LinkedHashMapは、コンストラクターに渡されたパラメーターに基づいてアクセス順序も維持します。順序を維持するために二重のリストを保持します(LinkedHashMap.Entryを参照)

@Pacerier LinkedHashMapは、要素が再び追加された場合、反復中に同じ順序を維持するのは正しいですが、それは挿入順序モードの場合のみです。

これはLinkedHashMap.EntryオブジェクトのJavaドキュメントで見つけたものです

    /**
     * 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

別の考えであり、JavaのLinkedHashMapコレクションを使用した単純な実装です。

LinkedHashMapが提供するメソッドremoveEldestEntryは、例で説明した方法でオーバーライドできます。デフォルトでは、このコレクション構造の実装はfalseです。この構造の実際のサイズが初期容量を超えると、最も古い要素または古い要素が削除されます。

私の場合、pagenoは整数で、pagecontentはページ番号の値の文字列を保持しています。

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 
0
Deepak Singhvi

Javaコードを使用して、より良いLRUキャッシュを探しています。 LinkedHashMapCollections#synchronizedMapを使用してJava LRUキャッシュコードを共有することは可能ですか?現在、私はLRUMap implements Mapを使用していますが、コードは正常に動作しますが、以下の方法で500人のユーザーを使用した負荷テストでArrayIndexOutofBoundExceptionを取得しています。このメソッドは、最近のオブジェクトをキューの先頭に移動します。

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
Raj Pandian