EHCacheやOSCacheなどは言わないでください。この質問の目的のために、SDKのみを使用して独自に実装することを想定します(実行して学習します)。キャッシュがマルチスレッド環境で使用される場合、どのデータ構造を使用しますか? LinkedHashMap および Collections#synchronizedMap を使用して既に実装していますが、新しい同時コレクションのいずれかがより良い候補になるかどうか興味があります。
更新:このナゲットを見つけたとき、私はただ読みました Yeggeの最新 :
一定時間のアクセスが必要で、挿入順序を維持したい場合は、本当に素晴らしいデータ構造であるLinkedHashMapを超えることはできません。おそらくもっと素晴らしい可能性がある唯一の方法は、並行バージョンがあった場合です。しかし悲しいかな。
上記のLinkedHashMap
+ Collections#synchronizedMap
実装を使用する前に、私はほぼ同じことを考えていました。何かを見落としていなかったことを知ってうれしいです。
これまでの回答に基づいて、高度に同時実行されるLRUに対する最善の策は、LinkedHashMap
が使用するものと同じロジックを使用して ConcurrentHashMap を拡張することです。
今日もこれを最初からやり直す場合、Guavaの CacheBuilder
を使用します。
私はこれらの提案がたくさん好きですが、今のところは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));
または、次のApache Commonsデータ構造を使用します。
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です。これは、今日のほとんどのサーバーで十分です。
中程度の並行性のもとで書くのがより簡単になり、より速くなります。
記述するのはより困難ですが、非常に高い同時実行性でははるかに優れた拡張性が得られます。通常のアクセスの場合は遅くなります(同時実行性がないConcurrentHashMap
はHashMap
よりも遅くなります)
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/
Java.util.concurrent.PriorityBlockingQueue を使用することを検討します。各要素の「numberOfUses」カウンターによって優先順位が決定されます。 「numberOfUses」カウンターとして、すべての同期を正しく取得するために、非常に、非常に慎重に要素は不変にできないことを意味します。
要素オブジェクトは、キャッシュ内のオブジェクトのラッパーになります。
class CacheElement {
private final Object obj;
private int numberOfUsers = 0;
CacheElement(Object obj) {
this.obj = obj;
}
... etc.
}
お役に立てれば 。
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);
}
}
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);
}
}
LRUの実装を次に示します。 PriorityQueueを使用しましたが、これは基本的にFIFOとして機能し、スレッドセーフではありません。ページ時間の作成に基づいて、使用されたコンパレータは、最も最近使用されていない時間のページの順序付けを実行します。
キャッシュに追加されるページは次のとおりです: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;
}
}
キャッシュについては、通常、プロキシオブジェクト(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変数を使用して実装できる最適化がいくつかあると確信しています。
これは私が使用する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();
}
}
}
以下は、同期ブロックなしでテストした最高のパフォーマンスの同時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);
}
}
ここに私の短い実装があります、それを批判または改善してください!
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);
}
}
この問題に対する私自身の実装は次のとおりです
simplelrucacheは、TTLをサポートする、スレッドセーフで非常にシンプルな非分散LRUキャッシングを提供します。次の2つの実装を提供します。
こちらで見つけることができます: http://code.google.com/p/simplelrucache/
ConcurrentSkipListMap をご覧ください。既にキャッシュに含まれている要素をテストおよび削除するためのlog(n)時間と、再追加するための一定の時間を提供する必要があります。
LRU順序の順序付けを強制し、キャッシュがいっぱいになったときに最新のものが破棄されるようにするには、カウンターなどとラッパー要素が必要です。
@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);
}
}
ハンクによって与えられた答えにコメントを追加したかったが、どうにかできない-コメントとして扱ってください
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の実装に最適なデータ構造です。
別の考えであり、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
Javaコードを使用して、より良いLRUキャッシュを探しています。 LinkedHashMap
とCollections#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
メソッドを呼び出します。