Есть ли SoftHashMap в Java?

Я знаю, что есть WeakHashMap в java.util, но так как он использует WeakReferences для всего, на что ссылается только это 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:

  1. Apache Commons: org.apache.commons.collections.map.ReferenceMap

  2. Коллекции Google: com.google.common.collect.ReferenceMap

В 98 выпуске бюллетеня для специалистов по Java приведен пример реализации.

Рассматривали ли вы использовать LRUMap вместо мягкой HashMap? Вы получаете больше контроля над тем, что хранится (или, по крайней мере, сколько).

Apache Shiro поставляется с SoftHashMap, предназначенным для кэширования. Он основан на статье, опубликованной jb выше и лицензированной под Apache v2. Вы можете найти документацию здесь и исходный код здесь.

Вы можете использовать, например, эту реализацию поточно-ориентированной 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,...) никогда не будут удалены, поскольку на них жестко ссылаются в кеше, но вы также будете придерживаться большего количества вещей (без контроля политики) до тех пор, пока так как памяти достаточно.

Другие вопросы по тегам