14

是否有一个简单、高效的Map实现,可以限制地图使用的内存。

我的用例是我想在创建时动态分配大部分可用内存,但我不想OutOFMemoryError在将来的任何时候。基本上,我想将此地图用作缓存,但我想避免像EHCache. 我的需求很简单(最多一个LRU算法)

我应该进一步澄清我的缓存中的对象是char[]或类似的原语,它们不会包含对其他对象的引用。

我可以为每个条目设置最大大小的上限。

4

5 回答 5

12

您可以使用 aLinkedHashMap来限制 中的条目数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

对于缓存, aSoftHashMap比 a 更合适WeakHashMap。WeakhashMap 通常用于当您希望与该对象保持关联时,只要该对象处于活动状态,但不阻止它被回收。

相比之下,aSoftReference与内存分配的关系更密切。看到没有 SoftHashMap?有关差异的详细信息。

WeakHashMap通常也不合适,因为它与错误的缓存方式相关联——它使用弱键和硬值。也就是说,当垃圾收集器清除键时,键和值将从映射中删除。这通常不是您想要的缓存 - 其中键通常是轻量级标识符(例如字符串或一些其他简单的值类型) - 缓存通常操作以便在清除引用时回收键/值。

Commons Collections 有一个ReferenceMap你可以插入你希望用于键和值的引用类型的地方。对于内存敏感的缓存,您可能会对键使用硬引用,对值使用软引用。

要获得给定数量 N 的 LRU 语义,请维护从缓存中获取的最后 N 个条目的列表 - 当从缓存中检索条目时,将其添加到列表的头部(并删除列表的尾部.) 为确保这不会占用太多内存,您可以创建一个软引用并将其用作触发器,以从列表末尾逐出一定百分比的条目。(并为下一个触发器创建一个新的软引用。)

于 2010-05-31T04:06:17.033 回答
3

Java 平台解决方案

如果您正在寻找的只是一个可以清理其键以避免的 Map OutOfMemoryErrors,您可能需要查看WeakHashMap。它使用Wea​​kReferences以允许垃圾收集器获取映射条目。但是,它不会强制执行任何类型的 LRU 语义,除了分代垃圾收集中存在的那些。

还有LinkedHashMap,在文档中有这个:

提供了一个特殊的构造函数来创建一个链接哈希映射,其迭代顺序是其条目最后一次访问的顺序,从最近最少访问到最近访问(访问顺序)。这种映射非常适合构建 LRU 缓存。调用 put 或 get 方法会导致访问相应的条目(假设它在调用完成后存在)。putAll 方法为指定映射中的每个映射生成一个条目访问,按照指定映射的条目集迭代器提供键值映射的顺序。没有其他方法生成条目访问。特别是,collection-views 上的操作不会影响 backing map 的迭代顺序。

因此,如果您使用这个构造函数来制作一个Iterator在 LRU 中迭代的地图,那么修剪地图就变得非常容易。一个(相当的)警告是 LinkedHashMap不同步,因此您需要自己处理并发问题。您可以将其包装在同步包装器中,但这可能会出现吞吐量问题。

推出自己的解决方案

如果我必须为这个用例编写自己的数据结构,我可能会创建某种数据结构,其中包含映射、队列ReadWriteLock以及一个看门人线程,以在映射中有太多条目时处理清理工作。可能会略微超过所需的最大尺寸,但在稳定状态下你会保持在它之下。

于 2010-05-31T03:55:10.377 回答
1

WeakHashMap不一定会达到您的目的,因为如果您的应用程序持有足够强的密钥引用。您将看到 OOME。

或者,您可以查看SoftReference,一旦堆稀缺,它将使内容无效。但是,我看到的大多数评论表明,在堆非常低并且大量 GC 开始启动并严重影响性能之前,它不会取消引用(因此我不建议将其用于您的目的) .

我的建议是使用简单的 LRU 映射,例如http://commons.apache.org/collections/apidocs/org/apache/commons/collections/LRUMap.html

于 2010-05-31T04:09:36.660 回答
0

谢谢大家的回复!

正如 jasonmp85 指出的那样,LinkedHashMap 有一个允许访问顺序的构造函数。当我查看 API 文档时,我错过了这一点。该实现看起来也很有效(见下文)。结合每个条目的最大尺寸上限,应该可以解决我的问题。

我还将仔细研究 SoftReference。仅作记录,Google Collections 似乎对 SoftKeys 和 SoftValues 和 Maps 有很好的 API。

这是 Java LikedHashMap 类的一个片段,展示了它们如何维护 LRU 行为。

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