Есть ли SoftHashMap в Java?
Я знаю, что есть WeakHashMap
в java.util
, но так как он использует WeakReference
s для всего, на что ссылается только это Map
ссылочные объекты будут потеряны в следующем цикле GC. Так что это почти бесполезно, если вы хотите кэшировать случайные данные, которые, скорее всего, будут запрашиваться снова, не будучи жестко связанными в остальное время. Лучшим решением будет карта, которая использует SoftReference
вместо этого, но я не нашел ни одного в пакете Java RT.
6 ответов
Изменить (август 2012 г.):
Оказывается, что в настоящее время лучшее решение, вероятно, Guava 13.0 Cache
классы, объясненные в вики Guava - это то, что я собираюсь использовать. Он даже поддерживает создание SoftHashMap
(увидеть CacheBuilder.newBuilder().softKeys()
), но это, вероятно, не то, что вы хотите, как объясняет эксперт по Java Джереми Мэнсон (ниже вы найдете ссылку).
Не то, что я знаю (ноябрь 2008 г.), но вы любезно найдете некоторую реализацию SoftHashMap
в сети.
Как этот: SoftHashMap
или этот.
Изменить (ноябрь 2009 г.)
Как упоминает Matthias в комментариях, Google Guava MapMaker использует SoftReferences:
ConcurrentMap
строитель, предоставляя любую комбинацию этих функций:
- мягкие или слабые клавиши,
- мягкие или слабые значения,
- истечение времени и
- вычисление значений по требованию.
Как уже упоминалось в этой теме, еще один кандидат в JSR166y:
jsr166y.ConcurrentReferenceHashMap
Он предоставляет альтернативную параллельную карту ссылок для реализации Google (которая использует фоновый поток для удаления записей)
Изменить (август 2012)
Реализация Google использует фоновый поток только тогда, когда запрошено истечение срока действия записей. В частности, он просто использует java.util.Timer
, что не так навязчиво, как наличие отдельного фонового потока.
Джереми Мэнсон рекомендует для любого кэша использовать эту функцию, чтобы избежать опасностей SoftReference: http://jeremymanson.blogspot.de/2009/07/how-hotspot-decides-to-clear_07.html
Есть еще одна реализация от Apache Commons, а именно org.apache.commons.collections.map.ReferenceMap; он не поддерживает удаление по времени, но он поддерживает выбор, следует ли сравнивать ключи по идентичности или по равенству. Более того, эта реализация не параллельна - ее можно синхронизировать, но она работает менее эффективно при доступах из нескольких потоков.
Я знаком с двумя библиотеками, которые предлагают реализацию SoftHashMap:
Apache Commons: org.apache.commons.collections.map.ReferenceMap
Коллекции Google: com.google.common.collect.ReferenceMap
В 98 выпуске бюллетеня для специалистов по Java приведен пример реализации.
Рассматривали ли вы использовать LRUMap вместо мягкой HashMap? Вы получаете больше контроля над тем, что хранится (или, по крайней мере, сколько).
Вы можете использовать, например, эту реализацию поточно-ориентированной Soft reference HashMap:
package cz.b2b.jcl.util;
import java.util.*;
import java.lang.ref.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.AbstractMap.SimpleImmutableEntry;
public class ConcurrentSoftHashMap<K, V> extends AbstractMap {
/**
The internal HashMap that will hold the SoftReference.
*/
private final Map<Object, SoftReference> hash = new ConcurrentHashMap<>();
/**
The number of "hard" references to hold internally.
*/
private final int HARD_SIZE;
/**
The FIFO list of hard references, order of last access.
*/
private final ConcurrentLinkedSetQueue hardCache = new ConcurrentLinkedSetQueue();
/**
Reference queue for cleared SoftReference objects.
*/
private final ReferenceQueue queue = new ReferenceQueue();
public ConcurrentSoftHashMap(int hardSize) {
HARD_SIZE = hardSize;
}
@Override
public Object get(Object key) {
Object result = null;
// We get the SoftReference represented by that key
SoftReference soft_ref = hash.get(key);
if (soft_ref != null) {
// From the SoftReference we get the value, which can be
// null if it was not in the map, or it was removed in
// the processQueue() method defined below
result = soft_ref.get();
if (result == null) {
// If the value has been garbage collected, remove the
// entry from the HashMap.
hash.remove(key);
} else {
// We now add this object to the beginning of the hard
// reference queue.
hardCache.enqueue(result);
if (hardCache.size() > HARD_SIZE) {
// Remove the last entry if list longer than HARD_SIZE
hardCache.dequeue();
}
}
}
return result;
}
/**
Here we put the key, value pair into the HashMap using
a SoftValue object.
@param key
@param value
@return
*/
@Override
public Object put(Object key, Object value) {
processQueue(); // throw out garbage collected values first
return hash.put(key, new SoftValue(value, key, queue));
}
@Override
public Object remove(Object key) {
processQueue(); // throw out garbage collected values first
return hash.remove(key);
}
@Override
public void clear() {
hardCache.clear();
processQueue(); // throw out garbage collected values
hash.clear();
}
@Override
public int size() {
processQueue(); // throw out garbage collected values first
return hash.size();
}
@Override
public boolean containsKey(Object key) {
processQueue(); // throw out garbage collected values first
return hash.containsKey(key);
}
@Override
public Set entrySet() {
Set<Map.Entry> entry = new HashSet<>();
Map.Entry simpleImmutableEntry = null;
Object result = null;
processQueue(); // throw out garbage collected values first
for (Map.Entry<Object, SoftReference> item : hash.entrySet()) {
if (item == null) {
continue;
}
Object key = item.getKey();
SoftReference soft_ref = item.getValue();
if (soft_ref != null) {
result = soft_ref.get();
if (result == null) {
hash.remove(key);
} else {
hardCache.enqueue(result);
if (hardCache.size() > HARD_SIZE) {
hardCache.dequeue();
}
simpleImmutableEntry = new SimpleImmutableEntry(key, result);
entry.add(simpleImmutableEntry);
}
}
}
return entry;
}
private class ConcurrentLinkedSetQueue<E> extends ConcurrentLinkedQueue<E> {
public void enqueue(E o) {
if (!contains(o)) {
add(o);
}
}
public E dequeue() {
return poll();
}
}
/**
We define our own subclass of SoftReference which contains
not only the value but also the key to make it easier to find
the entry in the HashMap after it's been garbage collected.
*/
private static class SoftValue extends SoftReference {
private final Object key; // always make data member final
/**
Did you know that an outer class can access private data
members and methods of an inner class? I didn't know that!
I thought it was only the inner class who could access the
outer class's private information. An outer class can also
access private members of an inner class inside its inner
class.
*/
private SoftValue(Object k, Object key, ReferenceQueue q) {
super(k, q);
this.key = key;
}
}
/**
Here we go through the ReferenceQueue and remove garbage
collected SoftValue objects from the HashMap by looking them
up using the SoftValue.key data member.
*/
private void processQueue() {
SoftValue sv;
while ((sv = (SoftValue) queue.poll()) != null) {
hash.remove(sv.key); // we can access private data!
}
}
}
Если вы хотите реализовать мягкие ссылки на кэш, это определенно лучшая идея, чем слабые ссылки, но она предоставляет всю вашу политику удаления кеша в руки сборщика мусора. что, вероятно, не то, что вы хотите.
Если политика удаления кэша важна, вам нужно будет сделать это самостоятельно, скорее всего, используя регулярные ссылки. Однако вам придется решить, когда извлекать предметы, а какие выбрасывать. Если вы хотите потерять что-то, только когда у вас заканчивается куча, вы можете запросить доступную кучу через:
Runtime.getRuntime().getFreeMemory();
Затем, когда объем свободной памяти упадет ниже определенного уровня, вы можете начать сбрасывать предметы. Или вы можете просто установить максимальный размер для кэша и использовать его, чтобы решить, когда отбрасывать вещи.
вот кэш LRU, который я разработал с O(1) временем вставки, удаления и поиска, который имеет настраиваемое максимальное количество элементов. Если вам нужен кеш, то это будет лучшим решением, чем SoftHashMap.
Softreferences - отличный способ создать расширяемый кеш. Таким образом, идеальным решением было бы использовать SoftHashMap вместе с обычным кэшем фиксированного размера. пусть все вставки в кеш идут как в фиксированный кеш, так и в мягкую хеш-карту, а затем для ссылки на что-то, просто посмотрите, есть ли это в мягкой хэш-карте (и обновите эталонное время в кеше). таким образом, все ваши самые важные элементы (в соответствии с выбранной вами политикой LRU, MFU,...) никогда не будут удалены, поскольку на них жестко ссылаются в кеше, но вы также будете придерживаться большего количества вещей (без контроля политики) до тех пор, пока так как памяти достаточно.