HashMap со слабыми значениями
Я реализую кеш для сохраняемых объектов. Идея такова:
- Метод
getObjectFromPersistence(long id); ///Takes about 3 seconds
- Метод
getObjectFromCache(long id) //Instantly
И иметь метод: getObject(long id)
со следующим псевдокодом:
synchronized(this){
CustomObject result= getObjectFromCache(id)
if (result==null){
result=getObjectFromPersistence(id);
addToCache(result);
}
return result;
}
Но мне нужно разрешить сборку CustomObject сборщиком мусора. До сих пор я использовал HashMap<Long,WeakReference<CustomObject>
для реализации. Проблема в том, что с течением времени HashMap заполняется пустым WeakReferences
.
Я проверил WeakHashMap, но там ключи слабы (и значения по-прежнему являются сильными ссылками), поэтому с longs с WeakReferences не имеют смысла.
Какое лучшее решение для решения этой проблемы? Есть ли какая-нибудь "обратная WeakHashMap" или что-то подобное?
Спасибо
Ответы
Ответ 1
Вы можете использовать Guava MapMaker
для этого:
ConcurrentMap<Long, CustomObject> graphs = new MapMaker()
.weakValues()
.makeMap();
Вы можете даже включить вычислительную часть, заменив makeMap()
следующим образом:
.makeComputingMap(
new Function<Long, CustomObject>() {
public CustomObject apply(Long id) {
return getObjectFromPersistence(id);
}
});
Поскольку то, что вы пишете, похоже на кеш, более новый, более специализированный Cache
(построенный с помощью CacheBuilder
) может быть еще более актуальным для вас. Он не реализует интерфейс Map
напрямую, но предоставляет еще больше элементов управления, которые могут потребоваться для кеша.
Ответ 2
A WeakReference
добавляется к ReferenceQueue
, предоставленному на время построения, когда его ссылка собрана.
Вы можете poll
ReferenceQueue
всякий раз, когда вы обращаетесь к кешу, и удерживайте HashMap<WeakReference<CustomObject>,Long>
, чтобы узнать, какую запись удалить, если ссылка найдена в очереди.
В качестве альтернативы, если кеш не используется часто, вы можете наблюдать за очередью в отдельном потоке.
Ответ 3
Вы пробовали android.util.LruCache
(его класс SDK11, но он также в пакете совместимости как android.support.v4.util.LruCache
). Он не реализует java.util.Map
, но работает как карта, и вы можете определить, сколько памяти потребуется, и оно будет стирать старые (неиспользуемые кешированные объекты сами по себе).
Ответ 4
Вы можете запускать "очистку" - вносить каждый раз в то время. Возможно, если ваш размер карты превышает пороговое значение, но максимум каждые 5 минут... что-то в этом роде.
Удерживайте циклы очистки короткими, чтобы не блокировать основные функции.
Ответ 5
Вы также можете протестировать WeakValueHashMap из jboss-common http://docs.jboss.org/jbossas/javadoc/4.0.2/org/jboss/util/collection/WeakValueHashMap.java.html
Ответ 6
Я думаю, что лучший вариант (если зависимость от Guava нежелательна) заключается в использовании пользовательского подкласса WeakReference, который запоминает его идентификатор, так что ваш поток очистки может удалить слабые значения во время очистки WeakReferences.
Реализация слабой ссылки с необходимым потоком ReferenceQueue и очистки будет выглядеть примерно так:
class CustomObjectAccess {
private static final ReferenceQueue<CustomObject> releasedCustomObjects =
new ReferenceQueue<>();
static {
Thread cleanupThread = new Thread("CustomObject cleanup thread")
while (true) {
CustomObjectWeakReference freed = (CustomObjectWeakReference)
CustomObjectWeakReference.releasedCustomObjects.remove();
cache.remove(freed.id);
}
};
cleanupThread.start();
}
private Map<CustomObjectID, CustomObjectWeakReference> cache;
public CustomObject get(CustomObjectID id) {
synchronized(this){
CustomObject result= getFromCache(id);
if (result==null) {
result=getObjectFromPersistence(id);
addToCache(result);
}
}
return result;
}
private addToCache(CustomObject co) {
cache.put(CustomObject.getID(), new CustomObjectWeakReference(co));
}
private getFromCache(CustomObjectID id) {
WeakReference<CustomObject> weak = cache.get(id);
if (weak != null) {
return weak.get();
}
return null;
}
class CustomObjectWeakReference extends WeakReference<CustomObject> {
private final CustomObjectID id;
CustomObjectWeakReference(CustomObject co) {
super(co, releasedCustomObjects);
this.id = co.getID();
}
}
}