Java-ориентированная карта/кеш с истекающими ключами

Знаете ли вы кого-либо из Java-карты или подобного стандартного хранилища данных, который автоматически удаляет записи после определенного таймаута? Это означает старение, когда старые истекшие записи "возрастают" автоматически.

Предпочтительно в библиотеке с открытым исходным кодом, доступной через Maven?

Я знаю о способах реализации функций самостоятельно и делал это несколько раз в прошлом, поэтому я не прошу совета в этом отношении, но для указателей на хорошую ссылочную реализацию.

WeakReference решения, такие как WeakHashMap не являются опцией, потому что мои ключи, скорее всего, будут неинтерминированными строками, и я хочу настраиваемый тайм-аут, который не зависит от сборщика мусора.

Ehcache также вариант, на который я бы не хотел полагаться, потому что ему нужны внешние файлы конфигурации. Я ищу решение только для кода.

Ответ 1

Да. В Google Collections, или Guava, как его теперь называют, есть что-то под названием MapMaker, которое может сделать именно это.

ConcurrentMap<Key, Graph> graphs = new MapMaker()
   .concurrencyLevel(4)
   .softKeys()
   .weakValues()
   .maximumSize(10000)
   .expiration(10, TimeUnit.MINUTES)
   .makeComputingMap(
       new Function<Key, Graph>() {
         public Graph apply(Key key) {
           return createExpensiveGraph(key);
         }
       });

Обновить:

Начиная с guava 10.0 (выпущена 28 сентября 2011 г.), многие из этих методов MapMaker устарели в пользу нового CacheBuilder:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(
        new CacheLoader<Key, Graph>() {
          public Graph load(Key key) throws AnyException {
            return createExpensiveGraph(key);
          }
        });

Ответ 2

ExpiringMap имеет аналогичные возможности, такие как Google Guava.

Map<String, Connection> map = ExpiringMap.builder()
  .expiration(30, TimeUnit.SECONDS)
  .build();

Ответ 3

Это примерная реализация, которую я сделал для одного и того же требования, и concurrency работает хорошо. Может быть полезно кому-то.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 
 * @author Vivekananthan M
 *
 * @param <K>
 * @param <V>
 */
public class WeakConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {

    private static final long serialVersionUID = 1L;

    private Map<K, Long> timeMap = new ConcurrentHashMap<K, Long>();
    private long expiryInMillis = 1000;
    private static final SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss:SSS");

    public WeakConcurrentHashMap() {
        initialize();
    }

    public WeakConcurrentHashMap(long expiryInMillis) {
        this.expiryInMillis = expiryInMillis;
        initialize();
    }

    void initialize() {
        new CleanerThread().start();
    }

    @Override
    public V put(K key, V value) {
        Date date = new Date();
        timeMap.put(key, date.getTime());
        System.out.println("Inserting : " + sdf.format(date) + " : " + key + " : " + value);
        V returnVal = super.put(key, value);
        return returnVal;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        for (K key : m.keySet()) {
            put(key, m.get(key));
        }
    }

    @Override
    public V putIfAbsent(K key, V value) {
        if (!containsKey(key))
            return put(key, value);
        else
            return get(key);
    }

    class CleanerThread extends Thread {
        @Override
        public void run() {
            System.out.println("Initiating Cleaner Thread..");
            while (true) {
                cleanMap();
                try {
                    Thread.sleep(expiryInMillis / 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void cleanMap() {
            long currentTime = new Date().getTime();
            for (K key : timeMap.keySet()) {
                if (currentTime > (timeMap.get(key) + expiryInMillis)) {
                    V value = remove(key);
                    timeMap.remove(key);
                    System.out.println("Removing : " + sdf.format(new Date()) + " : " + key + " : " + value);
                }
            }
        }
    }
}

Ура!!

Ответ 4

В Apache Commons есть декоратор для Map, срок действия которого истекает: PassiveExpiringMap Это проще, чем кеши из Guava.

PS будьте осторожны, это не синхронизировано.

Ответ 5

Вы можете попробовать мою реализацию самоэкспрессирующей хэш-карты. Эта реализация не использует потоки для удаления истекших записей, вместо этого использует DelayQueue, который автоматически очищается при каждой операции.

Ответ 6

В коллекциях Google (guava) есть MapMaker, в которых вы можете установить ограничение по времени (для истечения срока действия), и вы можете использовать мягкие или слабые как вы выбираете с помощью метода factory для создания экземпляров по вашему выбору.

Ответ 7

Похоже, что ehcache слишком завышен для того, что вы хотите, однако обратите внимание, что ему не нужны внешние файлы конфигурации.

Как правило, рекомендуется переместить конфигурацию в декларативные файлы конфигурации (поэтому вам не нужно перекомпилировать, когда для новой установки требуется другое время истечения срока действия), но это совсем не обязательно, вы все равно можете ее настроить программно. http://www.ehcache.org/documentation/user-guide/configuration

Ответ 8

Ключ Guava прост в реализации. Мы можем использовать ключ по времени с использованием кеша guava. Я прочитал полностью сообщение и ниже дает ключ от моего исследования.

cache = CacheBuilder.newBuilder().refreshAfterWrite(2,TimeUnit.SECONDS).
              build(new CacheLoader<String, String>(){
                @Override
                public String load(String arg0) throws Exception {
                    // TODO Auto-generated method stub
                    return addcache(arg0);
                }

              }

Ссылка: пример кэширования guava

Ответ 10

Как правило, кеш должен содержать объекты в течение некоторого времени и выставлять их через некоторое время. Хорошее время для хранения объекта зависит от варианта использования. Я хотел, чтобы эта вещь была простой, без потоков или планировщиков. Этот подход работает для меня. В отличие от SoftReference s, объекты гарантированно будут доступны в течение некоторого минимального времени. Однако, не останавливайтесь в памяти пока солнце не превратится в красного гиганта.

Как пример использования, подумайте о медленно реагирующей системе, которая сможет проверить, был ли запрос выполнен совсем недавно, и в этом случае не выполнять дважды запрошенное действие, даже если беспокойный пользователь несколько раз нажимает кнопку. Но, если такое же действие запрашивается через некоторое время, оно должно быть выполнено снова.

class Cache<T> {
    long avg, count, created, max, min;
    Map<T, Long> map = new HashMap<T, Long>();

    /**
     * @param min   minimal time [ns] to hold an object
     * @param max   maximal time [ns] to hold an object
     */
    Cache(long min, long max) {
        created = System.nanoTime();
        this.min = min;
        this.max = max;
        avg = (min + max) / 2;
    }

    boolean add(T e) {
        boolean result = map.put(e, Long.valueOf(System.nanoTime())) != null;
        onAccess();
        return result;
    }

    boolean contains(Object o) {
        boolean result = map.containsKey(o);
        onAccess();
        return result;
    }

    private void onAccess() {
        count++;
        long now = System.nanoTime();
        for (Iterator<Entry<T, Long>> it = map.entrySet().iterator(); it.hasNext();) {
            long t = it.next().getValue();
            if (now > t + min && (now > t + max || now + (now - created) / count > t + avg)) {
                it.remove();
            }
        }
    }
}

Ответ 11

Если кому-то нужна простая вещь, следующий - это простой набор ключей. Он может быть легко преобразован в карту.

public class CacheSet<K> {
    public static final int TIME_OUT = 86400 * 1000;

    LinkedHashMap<K, Hit> linkedHashMap = new LinkedHashMap<K, Hit>() {
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, Hit> eldest) {
            final long time = System.currentTimeMillis();
            if( time - eldest.getValue().time > TIME_OUT) {
                Iterator<Hit> i = values().iterator();

                i.next();
                do {
                    i.remove();
                } while( i.hasNext() && time - i.next().time > TIME_OUT );
            }
            return false;
        }
    };


    public boolean putIfNotExists(K key) {
        Hit value = linkedHashMap.get(key);
        if( value != null ) {
            return false;
        }

        linkedHashMap.put(key, new Hit());
        return true;
    }

    private static class Hit {
        final long time;


        Hit() {
            this.time = System.currentTimeMillis();
        }
    }
}