是否有一个简单、高效的Map
实现,可以限制地图使用的内存。
我的用例是我想在创建时动态分配大部分可用内存,但我不想OutOFMemoryError
在将来的任何时候。基本上,我想将此地图用作缓存,但我想避免像EHCache
. 我的需求很简单(最多一个LRU算法)
我应该进一步澄清我的缓存中的对象是char[]
或类似的原语,它们不会包含对其他对象的引用。
我可以为每个条目设置最大大小的上限。
是否有一个简单、高效的Map
实现,可以限制地图使用的内存。
我的用例是我想在创建时动态分配大部分可用内存,但我不想OutOFMemoryError
在将来的任何时候。基本上,我想将此地图用作缓存,但我想避免像EHCache
. 我的需求很简单(最多一个LRU算法)
我应该进一步澄清我的缓存中的对象是char[]
或类似的原语,它们不会包含对其他对象的引用。
我可以为每个条目设置最大大小的上限。
您可以使用 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; }
对于缓存, aSoftHashMap
比 a 更合适WeakHashMap
。WeakhashMap 通常用于当您希望与该对象保持关联时,只要该对象处于活动状态,但不阻止它被回收。
相比之下,aSoftReference
与内存分配的关系更密切。看到没有 SoftHashMap?有关差异的详细信息。
WeakHashMap
通常也不合适,因为它与错误的缓存方式相关联——它使用弱键和硬值。也就是说,当垃圾收集器清除键时,键和值将从映射中删除。这通常不是您想要的缓存 - 其中键通常是轻量级标识符(例如字符串或一些其他简单的值类型) - 缓存通常操作以便在清除值引用时回收键/值。
Commons Collections 有一个ReferenceMap
你可以插入你希望用于键和值的引用类型的地方。对于内存敏感的缓存,您可能会对键使用硬引用,对值使用软引用。
要获得给定数量 N 的 LRU 语义,请维护从缓存中获取的最后 N 个条目的列表 - 当从缓存中检索条目时,将其添加到列表的头部(并删除列表的尾部.) 为确保这不会占用太多内存,您可以创建一个软引用并将其用作触发器,以从列表末尾逐出一定百分比的条目。(并为下一个触发器创建一个新的软引用。)
如果您正在寻找的只是一个可以清理其键以避免的 Map OutOfMemoryErrors
,您可能需要查看WeakHashMap。它使用WeakReferences以允许垃圾收集器获取映射条目。但是,它不会强制执行任何类型的 LRU 语义,除了分代垃圾收集中存在的那些。
还有LinkedHashMap,在文档中有这个:
提供了一个特殊的构造函数来创建一个链接哈希映射,其迭代顺序是其条目最后一次访问的顺序,从最近最少访问到最近访问(访问顺序)。这种映射非常适合构建 LRU 缓存。调用 put 或 get 方法会导致访问相应的条目(假设它在调用完成后存在)。putAll 方法为指定映射中的每个映射生成一个条目访问,按照指定映射的条目集迭代器提供键值映射的顺序。没有其他方法生成条目访问。特别是,collection-views 上的操作不会影响 backing map 的迭代顺序。
因此,如果您使用这个构造函数来制作一个Iterator
在 LRU 中迭代的地图,那么修剪地图就变得非常容易。一个(相当大的)警告是 LinkedHashMap不同步,因此您需要自己处理并发问题。您可以将其包装在同步包装器中,但这可能会出现吞吐量问题。
如果我必须为这个用例编写自己的数据结构,我可能会创建某种数据结构,其中包含映射、队列ReadWriteLock
以及一个看门人线程,以在映射中有太多条目时处理清理工作。可能会略微超过所需的最大尺寸,但在稳定状态下你会保持在它之下。
WeakHashMap
不一定会达到您的目的,因为如果您的应用程序持有足够强的密钥引用。您将看到 OOME。
或者,您可以查看SoftReference
,一旦堆稀缺,它将使内容无效。但是,我看到的大多数评论表明,在堆非常低并且大量 GC 开始启动并严重影响性能之前,它不会取消引用(因此我不建议将其用于您的目的) .
我的建议是使用简单的 LRU 映射,例如http://commons.apache.org/collections/apidocs/org/apache/commons/collections/LRUMap.html
谢谢大家的回复!
正如 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);
}