是否存在一种简单、高效的 Map
实现,允许对地图使用的内存进行限制。
我的使用情况是:我想要在创建时动态地分配大多数可用内存,但我不想在未来的任何时候遇到 OutOFMemoryError
。基本上,我想将这个 map 用作缓存,但我要避免像 EHCache
这样的重型缓存实现。我的需求很简单(最多一个 LRU 算法)
我应进一步澄清,我的缓存中的对象是 char[]
或类似基元类型,它们不会持有其他对象的引用。
我可以为每个条目设置最大大小上限。
是否存在一种简单、高效的 Map
实现,允许对地图使用的内存进行限制。
我的使用情况是:我想要在创建时动态地分配大多数可用内存,但我不想在未来的任何时候遇到 OutOFMemoryError
。基本上,我想将这个 map 用作缓存,但我要避免像 EHCache
这样的重型缓存实现。我的需求很简单(最多一个 LRU 算法)
我应进一步澄清,我的缓存中的对象是 char[]
或类似基元类型,它们不会持有其他对象的引用。
我可以为每个条目设置最大大小上限。
您可以使用 LinkedHashMap
来限制 Map
中条目的数量:
removeEldestEntry(Map.Entry<K,V> eldest)
: Returnstrue
if this map should remove its eldest entry. This method is invoked byput
andputAll
after inserting a new entry into the map. It provides the implementor with the opportunity to remove the eldest entry each time a new one is added. This is useful if the map represents a cache: it allows the map to reduce memory consumption by deleting stale entries.Sample use: this override will allow the map to grow up to 100 entries and then delete the eldest entry each time a new entry is added, maintaining a steady state of 100 entries.
private static final int MAX_ENTRIES = 100; protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_ENTRIES; }
LinkedHashMap
作为一个选项。 - polygenelubricantsorg.apache.commons.collections.map.LRUMap
;你可以去看看。我实际上不太熟悉它。 - polygenelubricantsSoftHashMap
要比 WeakHashMap
更加适合。当你想要与一个对象保持关联,但不阻止其被回收时,通常使用 WeakhashMap。SoftReference
与内存分配更密切相关。有关差异的详细信息,请参见 No SoftHashMap?。
WeakHashMap
通常也不适用于缓存,因为它的关联方式对于缓存来说是错误的 - 它使用弱键和硬值。也就是说,当垃圾回收器清除了 键 时,键和值将从映射中删除。这通常不是缓存的预期行为 - 缓存通常会在 值 引用被清除时回收键/值。ReferenceMap
,可以插入您希望用于键和值的引用类型。对于内存敏感的缓存,您可能会为键使用强引用,为值使用软引用。如果您只是想要一个可以清除键以避免OutOfMemoryErrors
的Map,您可能需要查看WeakHashMap。它使用WeakReferences,以便允许垃圾收集器收回映射条目。但是,它不会强制执行任何LRU语义,除了在分代垃圾收集中存在的语义。
还有LinkedHashMap,文档中有这样的说明:
提供了一种特殊的构造函数来创建一个链接哈希映射,其迭代顺序是其条目上次被访问的顺序,从最近访问的到最近访问的(访问顺序)。这种映射非常适合构建LRU缓存。调用put或get方法会导致对相应条目的访问(假设在调用完成后存在)。putAll方法为指定映射中提供的每个映射生成一个条目访问,按照指定映射的条目集迭代器提供的键值映射的顺序。没有其他方法生成条目访问。特别是,集合视图上的操作不会影响支持映射的迭代顺序。
因此,如果您使用此构造函数创建一个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);
}