Часова карта / кеш на основі Java з клавішами, що закінчуються [закрито]


253

Хтось із вас знає про Java-карту або подібний стандартний сховище даних, який автоматично очищає записи після заданого часу? Це означає старіння, коли старі терміни з минулим терміном "старіють" автоматично.

Переважно у бібліотеці з відкритим кодом, до якої можна дістатися через Maven?

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

Рішення на основі WeakReference на зразок WeakHashMap не є варіантом, тому що мої ключі, ймовірно, є не інтернованими рядками, і я хочу настроювати тайм-аут, який не залежить від збирача сміття.

Ehcache - це також варіант, на який я не хотів би покладатися, оскільки йому потрібні зовнішні файли конфігурації. Я шукаю рішення для коду.


1
Перегляньте колекції Google (тепер називається Guava). На ній є карта, яка може автоматично припинити записи.
DTY

3
Як не дивно, що питання з 253 відгуками та 176 переглядами - що займає надто високу кількість пошукових систем за цією темою - було закрито, оскільки не відповідає вказівкам
Брайан

Відповіді:


320

Так. Колекції Google, або 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);
          }
        });

5
Дивовижно, я знав, що Гуава відповів, але не зміг її знайти! (+1)
Шон Патрік Флойд

12
Починаючи з v10, ви повинні використовувати CacheBuilder натомість ( guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/… ) з моменту закінчення терміну дії та інше було
вимкнено в MapMaker

49
Попередження ! Звідси weakKeys()випливає, що ключі порівнюються за допомогою семантики ==, а не equals(). Я втратив 30 хвилин, з'ясовуючи, чому мій кеш-клавіш не працює :)
Лоран Грегоар

3
Люди, те, про що згадував @Laurent, weakKeys()є важливим. weakKeys()не потрібно 90% часу.
Ману Манджунат

3
@ShervinAsgari заради початківців (включаючи мене), чи можете ви переключити оновлений приклад guava на той, який використовує кеш замість LoadingCache? Це відповідало б питання краще (оскільки LoadingCache має функції , які перевищують карту з закінчуються записи і набагато більш складно створити) см github.com/google/guava/wiki/CachesExplained#from-a-callable
Jeutnarg

29

Це вибіркова реалізація, яку я зробив для тієї ж вимоги, і одночасно працює добре. Може бути корисним для когось.

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


Посилання Git Repo (з реалізацією слухача)

https://github.com/vivekjustthink/WeakConcurrentHashMap

Ура !!


Чому ви виконуєте cleanMap()половину очікуваного часу?
EliuX

Bcoz забезпечує впевненість у тому, що клавіші закінчуються (видаляються), а також уникнення крайнього циклу.
Вівек

@Vivek, але при цьому реалізація може мати максимум (expiryInMillis / 2) кількість записів, які вже минули, але все ще присутні в кеші. Як нитка видаляє записи після закінчення термінуInMillis / 2
rishi007bansod

19

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


Мені більше подобається версія Гуави, але +1 за додання повноти картині
Шон Патрік Флойд

@ piero86 Я б сказав, що виклик delayQueue.poll () у методі expireKey (ExpiringKey <K> delayedKey) невірний. Ви можете втратити довільний ExpiringKey, який пізніше не можна використовувати при очищенні () - протікання.
Стефан Зобель

1
Ще одна проблема: ви не можете двічі ставити один і той же ключ за різні терміни життя. Після a) put (1, 1, shortLived), а потім b) put (1, 2, longLived) Запис в карті для ключа 1 буде відсутнє після короткого повідомлення ms незалежно від того, як довго longLived.
Стефан Зобель

Дякую за розуміння. Чи можете ви повідомити про ці проблеми як коментарі в суті, будь ласка?
pcan

Виправлено відповідно до ваших пропозицій. Дякую.
pcan

19

Apache Commons має декоратор для карти, який закінчує записи: PassiveExpiringMap Це простіше, ніж кеші з Guava.

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


1
Це просто, але він перевіряє час закінчення терміну дії лише після доступу до запису.
Badie

Відповідно до Javadoc : Коли викликає методи, які передбачають доступ до всього вмісту карти (тобто міститьKey (Об'єкт), entrySet () тощо), цей декоратор видаляє всі записи з минулим терміном до фактичного завершення виклику.
NS du Toit

Якщо ви хочете побачити, яка остання версія цієї бібліотеки (Apache commons commons-collection4) є посиланням на відповідну бібліотеку в mvnrepository
NS du Toit

3

Здається, що ehcache є надмірним для того, що ви хочете, однак зауважте, що він не потребує зовнішніх файлів конфігурації.

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


2

У колекціях Google (guava) є MapMaker, в якому ви можете встановити обмеження часу (для закінчення терміну дії), і ви можете використовувати м'які або слабкі посилання, коли ви вирішите використовувати заводський метод для створення обраних вами екземплярів.



2

Якщо комусь потрібна проста річ, наступним є простий набір ключів, що закінчується. Його можна легко перетворити на карту.

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
Це добре для ситуації, що складається з одного потоку, але вона би пошкодилась у випадку одночасної ситуації.
Шон Патрік Флойд

@SeanPatrickFloyd ти маєш на увазі, як сам LinkedHashMap ?! "вона повинна бути синхронізована зовні", як LinkedHashMap, HashMap ... ви її називаєте.
palindrom

так, як і всі ці, але на відміну від кеша Гуави (прийнята відповідь)
Шон Патрік Флойд

Крім того, розгляньте можливість використання System.nanoTime()для обчислення різниць у часі, оскільки System.currentTimeMillis () не є послідовним, оскільки залежить від системного часу та може бути не безперервним.
Ерксен

2

Як правило, кеш-пам'ять повинна зберігати об'єкти деякий час і викривати їх через деякий час. Який час утримувати об’єкт, залежить від випадку використання. Я хотів, щоб ця річ була простою, без потоків і планувальників. Такий підхід працює для мене. На відміну від SoftReferences, об'єкти гарантовано матимуть деяку мінімальну кількість часу. Однак не залишайтеся навколо в пам’яті, поки сонце не перетвориться на червоного гіганта .

Як приклад використання, подумайте про систему, що повільно реагує, яка зможе перевірити, чи було зроблено запит зовсім недавно, і в цьому випадку не виконувати запитувану дію двічі, навіть якщо неспокійний користувач натискає кнопку кілька разів. Але, якщо таку ж дію буде запрошено через деякий час, вона буде виконуватися знову.

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

приємно, дякую
bigbadmouse

1
HashMap не є безпечним для потоків, через умови перегонів, операція map.put або зміна розміру карти можуть призвести до пошкодження даних. Дивіться тут: mailinator.blogspot.com/2009/06/beautiful-race-condition.html
Євген Майсюк

Це правда. Дійсно, більшість класів Java не є безпечними для потоків. Якщо вам потрібна безпека потоку, вам потрібно перевірити кожен клас вашого дизайну, щоб перевірити, чи відповідає він вимозі.
Маттіас Ронге

1

Кеш-пам'ять 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


1
оновіть посилання, оскільки вона зараз не працює
smaiakov
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.