是否有一个简单,高效的Map
实现,允许地图使用内存限制。
我的用例是我想动态分配创建时可用的大部分内存,但我不希望将来随时OutOFMemoryError
。基本上,我想将此映射用作缓存,但我想避免像EHCache
这样的繁重缓存实现。我的需求很简单(最多是一个LRU算法)
我应该进一步澄清我的缓存中的对象是char[]
或类似的原语,它们不会保存对其他对象的引用。
我可以为每个条目设置最大大小的上限。
答案 0 :(得分:9)
您可以使用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; }
答案 1 :(得分:6)
对于缓存,SoftHashMap
比WeakHashMap
更合适。当您想要保持与对象的关联时,通常使用WeakhashMap,只要该对象处于活动状态,但不会阻止它被回收。
相比之下,SoftReference
更密切地涉及内存分配。有关差异的详细信息,请参阅No SoftHashMap?。
WeakHashMap
通常也不合适,因为它具有围绕错误的缓存方式的关联 - 它使用弱键和硬值。也就是说,当垃圾收集器清除键时,键和值将从地图中删除。这通常不是您想要的缓存 - 其中键通常是轻量级标识符(例如字符串或其他一些简单的值类型) - 缓存通常操作使得当值参考被清除。
Commons Collections有一个ReferenceMap
,您可以在其中插入您希望用于键和值的引用类型。对于对内存敏感的缓存,您可能会使用硬引用键和值的软引用。
要获取给定数量的引用N的LRU语义,请维护从缓存中获取的最后N个条目的列表 - 当从缓存中检索条目时,它将被添加到列表的头部(以及删除列表。)为了确保不会占用太多内存,您可以创建一个软引用,并将其用作触发器,从列表末尾逐出百分比的条目。 (并为下一次触发创建一个新的软参考。)
答案 2 :(得分:3)
如果你要找的是一张地图,可以清理其密钥以避免OutOfMemoryErrors
,你可能需要查看WeakHashMap。它使用WeakReferences以允许垃圾收集器获取映射条目。但是,它不会强制执行任何类型的LRU语义,除了代际垃圾收集中存在的那些。
还有LinkedHashMap,在文档中有这个:
提供了一个特殊的构造函数 创建一个其顺序的链接哈希映射 迭代的顺序是它的顺序 上次访问的条目来自 最近访问过的 最近(访问顺序)。这个 一种地图非常适合建筑 LRU缓存。调用put或get 方法导致访问 相应的条目(假设它 调用后存在 完成)。 putAll方法 为每个生成一个条目访问权限 映射在指定的映射中 命令键值映射 由指定地图的条目提供 设置迭代器。没有其他方法 生成入口访问。在 特别是,开展业务 集合视图不会影响 支持地图的迭代顺序。
因此,如果您使用此构造函数创建Iterator
在LRU中迭代的地图,则修剪地图变得非常容易。一个(相当大)警告是LinkedHashMap 无法同步,因此您可以自己进行并发。您可以将其包装在同步包装器中,但这可能会产生吞吐量问题。
如果我必须为这个用例编写自己的数据结构,我可能会创建一些带有map,queue和ReadWriteLock
的数据结构以及一个janitor线程来处理清理时的情况。地图中有太多条目。有可能略微超过所需的最大尺寸,但在稳定状态下,你会留在它下面。
答案 3 :(得分:1)
WeakHashMap
不一定会达到您的目的,因为如果您的应用程序持有对密钥的足够强引用,您将会看到OOME。
或者你可以查看SoftReference
,一旦堆稀缺,它将使内容无效。但是,我看到的大多数注释表明它不会使引用无效,直到堆非常低并且很多GC开始以严重的性能命中(因此我不建议将其用于您的目的) 。
我的建议是使用简单的LRU地图,例如http://commons.apache.org/collections/apidocs/org/apache/commons/collections/LRUMap.html
答案 4 :(得分:0)
感谢回复家伙们!
正如jasonmp85所指出的,LinkedHashMap有一个允许访问顺序的构造函数。当我查看API文档时,我错过了那一点。实施也看起来非常有效(见下文)。结合每个条目的最大大小上限,这应该可以解决我的问题。
我还将仔细研究SoftReference。仅作为记录,Google Collections似乎拥有相当不错的SoftKeys和SoftValues以及地图的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);
}