2010-09-27 2 views
175

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

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

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

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

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

+1

Ознакомьтесь с коллекциями Google (теперь называется Guava). Он имеет карту, которая может автоматически включать таймауты. – dty

ответ

238

Да. 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); 
     } 
     }); 

Обновление:

Как гуавы 10,0 (выпущен 28 сентября 2011 г.) многие из этих методов Mapmaker устарели в пользу нового CacheBuilder:

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

Удивительный, я знал, что у Гува есть ответ, но я не мог его найти! (+1) –

+0

Я знаю, что вы только что скопировали пример из javadoc, но поскольку я столкнулся с тем же требованием, я просто хочу убедиться, что свойства карты верны: не было бы лучше использовать 'expireAfterAccess' (вместо 'expiration') и' softValues' (вместо 'weakValues')? – Kariem

+12

Начиная с версии 10, вы должны использовать CacheBuilder вместо этого (http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/cache/CacheBuilder.html), так как истечение срока и т. Д. Устарели в MapMaker – wwadge

3

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

2

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

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

30

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

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

Apache Commons имеет декоратор для карт истекает enties: PassiveExpiringMap Это более простой, чем кэшей из гуавы.

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

12

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

+0

Мне нравится версия Гуавы лучше, но +1 для добавления полноты к картинке –

+0

@ piero86 Я бы сказал, что вызов delayQueue.poll() в методе expireKey (ExpiringKey delayedKey) неверен. Вы можете потерять произвольный ExpiringKey, который впоследствии не может быть использован при очистке() - утечке. –

+1

Другая проблема: вы не можете поместить одну и ту же клавишу дважды с разными сроками службы. После a) put (1, 1, shortLived), затем b) put (1, 2, longLived) запись Map для ключа 1 исчезнет после shortLived ms независимо от того, как долго longLived. –

0

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

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(); 
     } 
    } 
} 
+2

Это нормально для ситуации с одним потоком, но это может сломаться в одиночной ситуации. –

+0

@SeanPatrickFloyd вы имеете в виду, как LinkedHashMap !? «он должен быть синхронизирован извне», как LinkedHashMap, HashMap ... вы называете его. – palindrom

+0

Да, как и все, но в отличие от кеша Гуавы (принятый ответ) –

2

Ключ 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 cache example

+0

Правда, но во время моего вопроса кеша Guava еще не существовало –

0

Как правило, кэш должен хранить объекты вокруг некоторое времени и выставит из них через некоторое время. Что такое - хорошее время для хранения объекта зависит от варианта использования. Я хотел, чтобы эта вещь была простой, без потоков или планировщиков. Этот подход работает для меня. В отличие от SoftReference, объекты гарантированно будут доступны в течение минимального промежутка времени. Тем не менее, они не остаются в памяти until the sun turns into a red giant.

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

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(); 
      } 
     } 
    } 
} 
16

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

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); 
       } 
      } 
     } 
    } 
} 

Приветствия!

+0

Почему вы выполняете 'cleanMap()' половину времени expecified? – EliuX

+0

Bcoz гарантирует, что ключи истекли (удалены) и избегают потоков с крайнего цикла. – Vivek

Смежные вопросы