期限切れのキーを持つJava時間ベースのマップ/キャッシュ[終了]


253

特定のタイムアウト後にエントリを自動的にパージするJavaマップまたは同様の標準データストアを知っている人はいますか?これは、古い期限切れのエントリが自動的に「期限切れ」になるエージングを意味します。

Maven経由でアクセスできるオープンソースライブラリが望ましいですか?

私は自分で機能を実装する方法を知っており、過去に何度かそれを行ったので、その点でアドバイスを求めるのではなく、適切なリファレンス実装へのポインタを求めています。

WeakHashMapなどのWeakReferenceベースのソリューションはオプションではありません。キーがインターンされていない文字列である可能性が高く、ガベージコレクターに依存しない構成可能なタイムアウトが必要だからです。

Ehcacheも外部設定ファイルを必要とするため、信頼したくないオプションです。コードのみのソリューションを探しています。


1
Googleコレクション(現在はGuava)を確認してください。エントリを自動的にタイムアウトできるマップがあります。
10

3
253件の賛成投票と176万回の表示(このトピックの検索エンジンで超上位にランク付け)のある質問が、ガイドラインを満たしていないためにクローズされたのは奇妙なことです
ブライアン

回答:


320

はい。Googleコレクション、またはGuavaの名前には、MapMakerと呼ばれるものがあります。これはまさにそれを実行できます。

ConcurrentMap<Key, Graph> graphs = new MapMaker()
   .concurrencyLevel(4)
   .softKeys()
   .weakValues()
   .maximumSize(10000)
   .expiration(10, TimeUnit.MINUTES)
   .makeComputingMap(
       new Function<Key, Graph>() {
         public Graph apply(Key key) {
           return createExpensiveGraph(key);
         }
       });

更新:

guava 10.0(2011年9月28日リリース)以降、これらのMapMakerメソッドの多くは廃止され、新しいCacheBuilderが採用されています

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(
        new CacheLoader<Key, Graph>() {
          public Graph load(Key key) throws AnyException {
            return createExpensiveGraph(key);
          }
        });

5
すごい、グアバに答えがあることは知っていましたが、見つかりませんでした!(+1)
ショーンパトリックフロイド

12
v10 以降、MapBuilderで有効期限などが廃止されたため、代わりにCacheBuilder(guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/…)を使用する必要があります
wwadge

49
警告!を使用weakKeys()すると、キーは=ではなく、==セマンティクスを使用して比較されますequals()。String-keyedキャッシュが機能しない理由を理解するのに30分かかりました:)
LaurentGrégoire2013年

3
フォーク、@ Laurentが言及したことweakKeys()は重要です。weakKeys()時間の90%は必要ありません。
Manu Manjunath 2015年

3
初心者のために@ShervinAsgari(自分も含む)の場合、更新したguavaの例を、LoadingCacheの代わりにCacheを使用するものに切り替えることはできますか?それは質問によりよく一致します(LoadingCacheには期限切れのエントリを持つマップを超える機能があり、作成がはるかに複雑であるため)github.com/google/guava/wiki/CachesExplained#from-a-callable
Jeutnarg

29

これは、同じ要件に対して実行したサンプル実装であり、同時実行性はうまく機能します。誰かに役立つかもしれません。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 
 * @author Vivekananthan M
 *
 * @param <K>
 * @param <V>
 */
public class WeakConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {

    private static final long serialVersionUID = 1L;

    private Map<K, Long> timeMap = new ConcurrentHashMap<K, Long>();
    private long expiryInMillis = 1000;
    private static final SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss:SSS");

    public WeakConcurrentHashMap() {
        initialize();
    }

    public WeakConcurrentHashMap(long expiryInMillis) {
        this.expiryInMillis = expiryInMillis;
        initialize();
    }

    void initialize() {
        new CleanerThread().start();
    }

    @Override
    public V put(K key, V value) {
        Date date = new Date();
        timeMap.put(key, date.getTime());
        System.out.println("Inserting : " + sdf.format(date) + " : " + key + " : " + value);
        V returnVal = super.put(key, value);
        return returnVal;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        for (K key : m.keySet()) {
            put(key, m.get(key));
        }
    }

    @Override
    public V putIfAbsent(K key, V value) {
        if (!containsKey(key))
            return put(key, value);
        else
            return get(key);
    }

    class CleanerThread extends Thread {
        @Override
        public void run() {
            System.out.println("Initiating Cleaner Thread..");
            while (true) {
                cleanMap();
                try {
                    Thread.sleep(expiryInMillis / 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void cleanMap() {
            long currentTime = new Date().getTime();
            for (K key : timeMap.keySet()) {
                if (currentTime > (timeMap.get(key) + expiryInMillis)) {
                    V value = remove(key);
                    timeMap.remove(key);
                    System.out.println("Removing : " + sdf.format(new Date()) + " : " + key + " : " + value);
                }
            }
        }
    }
}


Git Repo Link(リスナー実装あり)

https://github.com/vivekjustthink/WeakConcurrentHashMap

乾杯!!


なぜあなたはcleanMap()半分の時間を実行するのですか?
EliuX 2017年

Bcozは、キーの有効期限が切れている(削除されている)ことを確認し、スレッドが極端にループしないようにします。
Vivek 2017年

@Vivekですが、この実装では、最大(expiryInMillis / 2)のエントリがすでに期限切れになっているが、まだキャッシュに存在する場合があります。スレッドがexpiryInMillis / 2期間後にエントリを削除するため
rishi007bansod '17

19

自己期限切れのハッシュマップの私の実装を試すことができます。この実装は、期限切れのエントリを削除するためにスレッドを使用しません。代わりに、すべての操作で自動的にクリーンアップされるDelayQueueを使用します。


私はグアバのバージョンの方が好きですが、写真に完全性を加えるために+1
ショーンパトリックフロイド

@ piero86メソッドexpireKey(ExpiringKey <K> delayKey)のdelayQueue.poll()の呼び出しは間違っていると思います。後でcleanup()で利用できない任意のExpiringKeyを失う可能性があります-リーク。
Stefan Zobel

1
別の問題:ライフタイムの異なる同じキーを2回置くことはできません。a)put(1、1、shortLived)の後、b)put(1、2、longLived)キー1のマップエントリは、longLivedがどれだけ長くても、shortLived ms後に消えます。
Stefan Zobel 2016年

あなたの洞察をありがとう。これらの問題を要点のコメントとして報告してもらえますか?
pcan

あなたの提案に従って修正されました。ありがとう。
pcan

19

Apache Commonsには、エントリを期限切れにするMapのデコレータがあります:PassiveExpiringMap これは、Guavaのキャッシュよりも簡単です。

PSは注意してください、それは同期されていません。


1
シンプルですが、エントリーにアクセスした後にのみ有効期限をチェックします。
Badie

あたりとしてのJavadoc伴う呼び出すメソッドは、マップ全体の内容(つまり、などのcontainsKey(オブジェクト)、のentrySetを()、)にアクセスすると、このデコレータは、実際に呼び出しを完了する前に期限切れになったすべてのエントリを削除します。
NS du Toit

あなたはこのライブラリの最新バージョン(Apacheのコモンズコモンズ-collections4)があるかを確認したい場合は、ここでmvnrepositoryに関連するライブラリーへのリンクです
NSデュToit

3

ehcacheのような音はあなたが望むものには過剰ですが、外部構成ファイルを必要としないことに注意してください。

一般に、構成を宣言的な構成ファイルに移動することをお勧めします(そのため、新しいインストールで別の有効期限が必要な場合に再コンパイルする必要はありません)。 http://www.ehcache.org/documentation/user-guide/configuration


2

Googleコレクション(グアバ)には、(期限切れの)時間制限を設定できるマップメーカーがあり、ファクトリメソッドを使用して選択したインスタンスを作成するときに、ソフトまたは弱参照を使用できます。



2

誰かが単純なものを必要とする場合、以下は単純なキー期限切れセットです。簡単に地図に変換できるかもしれません。

public class CacheSet<K> {
    public static final int TIME_OUT = 86400 * 1000;

    LinkedHashMap<K, Hit> linkedHashMap = new LinkedHashMap<K, Hit>() {
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, Hit> eldest) {
            final long time = System.currentTimeMillis();
            if( time - eldest.getValue().time > TIME_OUT) {
                Iterator<Hit> i = values().iterator();

                i.next();
                do {
                    i.remove();
                } while( i.hasNext() && time - i.next().time > TIME_OUT );
            }
            return false;
        }
    };


    public boolean putIfNotExists(K key) {
        Hit value = linkedHashMap.get(key);
        if( value != null ) {
            return false;
        }

        linkedHashMap.put(key, new Hit());
        return true;
    }

    private static class Hit {
        final long time;


        Hit() {
            this.time = System.currentTimeMillis();
        }
    }
}

2
これはシングルスレッドの状況では問題ありませんが、並行状況では無惨に壊れます。
ショーンパトリックフロイド

@SeanPatrickFloydあなたはLinkedHashMap自体のようですか?!LinkedHashMap、HashMapと同じように、「外部で同期する必要があります...」という名前を付けます。
palindrom 2015

はい、そうですが、グアバのキャッシュ(受け入れられた回答)とは異なります
ショーンパトリックフロイド

また、System.nanoTime()System.currentTimeMillis()はシステム時間に依存し、連続的でない可能性があるため、一貫性がないため、時間差の計算に使用することを検討してください。
Ercksen

2

通常、キャッシュはオブジェクトをしばらくの間保持し、後でオブジェクトを公開する必要があります。オブジェクトを保持する適切なタイミングは、ユースケースによって異なります。私はこのことを単純なものにしたかったので、スレッドやスケジューラは必要ありませんでした。このアプローチは私にとってはうまくいきます。とは異なりSoftReference、オブジェクトは最小限の時間で利用できることが保証されています。ただし、太陽が赤い巨人に変わるまでは、記憶に残りません。

使用例として、要求がごく最近行われたかどうかを確認でき、多忙なユーザーがボタンを数回押した場合でも、要求されたアクションを2回実行しない、応答の遅いシステムを考えます。ただし、後で同じアクションが要求された場合は、再度実行する必要があります。

class Cache<T> {
    long avg, count, created, max, min;
    Map<T, Long> map = new HashMap<T, Long>();

    /**
     * @param min   minimal time [ns] to hold an object
     * @param max   maximal time [ns] to hold an object
     */
    Cache(long min, long max) {
        created = System.nanoTime();
        this.min = min;
        this.max = max;
        avg = (min + max) / 2;
    }

    boolean add(T e) {
        boolean result = map.put(e, Long.valueOf(System.nanoTime())) != null;
        onAccess();
        return result;
    }

    boolean contains(Object o) {
        boolean result = map.containsKey(o);
        onAccess();
        return result;
    }

    private void onAccess() {
        count++;
        long now = System.nanoTime();
        for (Iterator<Entry<T, Long>> it = map.entrySet().iterator(); it.hasNext();) {
            long t = it.next().getValue();
            if (now > t + min && (now > t + max || now + (now - created) / count > t + avg)) {
                it.remove();
            }
        }
    }
}

nice、ありがとう
bigbadmouse

1
競合状態のため、HashMapはスレッドセーフではありません。map.put操作またはマップのサイズ変更により、データが破損する可能性があります。こちらをご覧ください:mailinator.blogspot.com/2009/06/beautiful-race-condition.html
Eugene Maysyuk

それは本当だ。実際、ほとんどのJavaクラスはスレッドセーフではありません。スレッドセキュリティが必要な場合は、影響を受けるデザインのすべてのクラスをチェックして、それが要件を満たしているかどうかを確認する必要があります。
Matthias Ronge

1

グアバキャッシュは実装が簡単です。グアバキャッシュを使用して、タイムベースでキーを期限切れにすることができます。私は完全に投稿を読みました、そして以下は私の研究の鍵を与えます。

cache = CacheBuilder.newBuilder().refreshAfterWrite(2,TimeUnit.SECONDS).
              build(new CacheLoader<String, String>(){
                @Override
                public String load(String arg0) throws Exception {
                    // TODO Auto-generated method stub
                    return addcache(arg0);
                }

              }

参考:グアバキャッシュの例


1
リンクが機能していないため、リンクを更新してください
smaiakov
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.