14

Mapマップが使用するメモリを制限できるシンプルで効率的な実装はありますか。

OutOFMemoryError私のユースケースは、作成時に使用可能なメモリのほとんどを動的に割り当てたいが、将来はいつでも割り当てたくないということです。基本的にはこのマップをキャッシュとして使いたいのですが、 のような重いキャッシュの実装は避けたいEHCacheです。私のニーズは単純です(せいぜいLRUアルゴリズム)

私のキャッシュ内のオブジェクトは、char[]他のオブジェクトへの参照を保持しない、または同様のプリミティブであることをさらに明確にする必要があります。

各エントリの最大サイズに上限を設定できます。

4

5 に答える 5

12

を使用しLinkedHashMapて、 のエントリ数を制限できMapます。

removeEldestEntry(Map.Entry<K,V> eldest)true:このマップが最も古いエントリを削除する必要があるかどうかを返します。このメソッドは、新しいエントリをマップに挿入した後、put呼び出されます。putAll新しいエントリが追加されるたびに、最も古いエントリを削除する機会を実装者に提供します。これは、マップがキャッシュを表す場合に便利です。古いエントリを削除することで、マップのメモリ消費を減らすことができます。

使用例: このオーバーライドにより、マップは最大 100 エントリまで拡張され、新しいエントリが追加されるたびに最も古いエントリが削除され、100 エントリの安定した状態が維持されます。

private static final int MAX_ENTRIES = 100;

protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_ENTRIES;
}

関連する質問

于 2010-05-31T03:53:43.577 に答える
6

キャッシュの場合、SoftHashMapは よりもはるかに適切ですWeakHashMap。通常、WeakhashMap は、オブジェクトが生きている限りそのオブジェクトとの関連付けを維持したいが、再利用を妨げない場合に使用されます。

対照的に、 aSoftReferenceはメモリ割り当てにより密接に関係しています。SoftHashMapを参照してください。違いの詳細については。

WeakHashMapまた、キャッシュに対して間違った方法で関連付けられているため、通常は適切ではありません。弱いキーとハード値を使用します。つまり、ガベージ コレクタによってキーがクリアされると、キーと値がマップから削除されます。これは通常、キャッシュに必要なものではありません。通常、キーは軽量の識別子 (文字列やその他の単純な値の型など) です。キャッシュは通常、の参照がクリアされたときにキー/値が再利用されるように動作します。

Commons Collections には、ReferenceMapキーと値に使用する参照の種類をプラグインできる場所があります。メモリに依存するキャッシュの場合、おそらくキーにはハード参照を使用し、値にはソフト参照を使用します。

特定の参照数 N の LRU セマンティクスを取得するには、キャッシュからフェッチされた最後の N エントリのリストを維持します。キャッシュからエントリが取得されると、リストの先頭に追加されます (リストの末尾は削除されます)。 .) これが大量のメモリを保持しないようにするために、ソフト参照を作成し、それをトリガーとして使用して、リストの最後からエントリのパーセンテージを削除できます。(そして、次のトリガー用に新しいソフト参照を作成します。)

于 2010-05-31T04:06:17.033 に答える
3

Java プラットフォーム ソリューション

キーをクリーンアップして回避できる Map だけを探している場合は、WeakHashMapOutOfMemoryErrorsを調べることをお勧めします。ガベージ コレクターがマップ エントリを取得できるようにするために、WeakReferencesを使用します。ただし、世代別ガベージ コレクションに存在するものを除いて、いかなる種類の LRU セマンティクスも強制しません。

ドキュメントにこれがあるLinkedHashMapもあります:

リンクされたハッシュ マップを作成するための特別なコンストラクターが提供されます。この反復順序は、そのエントリが最後にアクセスされた順序 (最も古いアクセスから最近のアクセスの順序 (アクセス順序) です) になります。この種のマップは、LRU キャッシュの構築に適しています。put メソッドまたは get メソッドを呼び出すと、対応するエントリにアクセスできます (呼び出しの完了後にエントリが存在すると仮定します)。putAll メソッドは、指定されたマップのエントリ セット イテレータによってキーと値のマッピングが提供される順序で、指定されたマップ内のマッピングごとに 1 つのエントリ アクセスを生成します。エントリ アクセスを生成する他のメソッドはありません。特に、コレクション ビューに対する操作は、バッキング マップの反復の順序には影響しません。

したがって、このコンストラクターを使用してIterator、LRU で反復するマップを作成すると、マップのプルーニングが非常に簡単になります。1 つの (かなり大きな) 注意点は、LinkedHashMap がまったく同期されていないことです。同期ラッパーでラップすることもできますが、スループットの問題が発生する可能性があります。

独自のソリューションを展開する

ReadWriteLockこのユース ケースで独自のデータ構造を作成する必要がある場合は、マップ、キュー、およびマップ内のエントリが多すぎる場合のクリーンアップを処理する用務員スレッドを使用して、ある種のデータ構造を作成することになるでしょう。希望の最大サイズをわずかに超えることは可能ですが、定常状態ではそれ以下にとどまります。

于 2010-05-31T03:55:10.377 に答える
1

WeakHashMapキーへの十分な強力な参照がアプリ​​によって保持されている場合、OOME が表示されるため、必ずしも目的を達成するとは限りません。

SoftReferenceまたは、ヒープが不足するとコンテンツを無効にする を調べることもできます。ただし、私が見たコメントのほとんどは、ヒープが本当に低くなり、多くの GC が開始されてパフォーマンスが大幅に低下するまで参照を無効にしないことを示しています (したがって、目的に使用することはお勧めしません)。 .

単純な LRU マップを使用することをお勧めします。

于 2010-05-31T04:09:36.660 に答える
0

返信ありがとう!

jasonmp85 が指摘したように、LinkedHashMap にはアクセス順序を許可するコンストラクターがあります。API ドキュメントを見たときに、そのビットを見逃していました。実装も非常に効率的です (以下を参照)。各エントリの最大サイズの上限と組み合わせると、問題が解決するはずです。

SoftReference についても詳しく見ていきます。記録のためだけに、Google Collections は一般的に SoftKeys と SoftValues と Maps のためのかなり良い API を持っているようです。

これは、LRU 動作を維持する方法を示す Java LikedHashMap クラスのスニペットです。

    /**
     * Removes this entry from the linked list.
     */
    private void remove() {
        before.after = after;
        after.before = before;
    }

    /**
     * Inserts this entry before the specified existing entry in the list.
     */
    private void addBefore(Entry<K,V> existingEntry) {
        after  = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
    }

    /**
     * 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);
        }
于 2010-06-02T21:19:28.290 に答える