Süresi dolan anahtarlarla Java zaman tabanlı harita / önbellek [kapalı]


253

Herhangi biriniz belirli bir zaman aşımından sonra girişleri otomatik olarak temizleyen bir Java Haritası veya benzer bir standart veri deposu biliyor musunuz? Bu, eski sürenin dolduğu girişlerin otomatik olarak “yaşlandığını” yaşlanma anlamına gelir.

Tercihen Maven?

İşlevselliği kendim uygulamanın yollarını biliyorum ve geçmişte birkaç kez yaptım, bu yüzden bu konuda tavsiye istemiyorum, ancak iyi bir referans uygulamasına işaret ediyorum.

WeakHashMap gibi WeakReference tabanlı çözümler bir seçenek değildir, çünkü anahtarlarımın interned olmayan dizeler olması olasıdır ve çöp toplayıcısına bağlı olmayan yapılandırılabilir bir zaman aşımı istiyorum.

Ehcache , harici yapılandırma dosyalarına ihtiyaç duyduğu için güvenmek istemediğim bir seçenek. Ben sadece kod çözümü arıyorum.


1
Google Koleksiyonlarına (şimdi Guava adı verilir) göz atın. Girişleri otomatik olarak zaman aşımına uğratabilen bir haritaya sahiptir.
dty

3
Bu konu için arama motorlarında süper üst sıralarda yer alan 253 upvotes ve 176k görüntülemeli bir sorunun, kurallara uymadığı için ne kadar tuhaf
Brian

Yanıtlar:


320

Evet. Google Koleksiyonları veya Guava Adını olarak şimdi denilen bir şey vardır MapMaker tam olarak bunu yapabilirsiniz.

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

Güncelleme:

Guava 10.0 (28 Eylül 2011'de yayımlandı) itibariyle, bu MapMaker yöntemlerinin çoğu yeni CacheBuilder lehine kullanımdan kaldırılmıştır :

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
Harika, Guava'nın bir cevabı olduğunu biliyordum ama bulamadım! (+1)
Sean Patrick Floyd

12
V10 itibaren, bunun yerine CacheBuilder (kullanarak olmalıdır guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/... vb MapMaker onaylanmaz süresi bittiği tarihten itibaren)
wwadge

49
Uyarı ! Kullanma weakKeys()tuşları, == anlamsal değil kullanılarak karşılaştırılır ima equals(). String anahtarlı önbelleğimin neden çalışmadığını bulmak için 30 dakika kaybettim :)
Laurent Grégoire

3
Millet, @Laurent'ın bahsettiği şey weakKeys()önemlidir. weakKeys()% 90 oranında gerekli değildir.
Manu Manjunath

3
@ShervinAsgari (kendim dahil) uğruna, güncellenmiş guava örneğinizi LoadingCache yerine Önbellek kullanan bir tanesiyle değiştirebilir misiniz? Soruyu daha iyi eşleştirebilir (LoadingCache'in süresi dolmuş girişleri olan bir haritayı aşan ve oluşturulması çok daha karmaşık özelliklere sahip olduğundan) bkz. Github.com/google/guava/wiki/CachesExplained#from-a-callable
Jeutnarg

29

Bu aynı gereklilik için yaptığım örnek bir uygulama ve eşzamanlılık iyi çalışıyor. Birisi için yararlı olabilir.

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 Bağlantısı (Dinleyici Uygulaması ile)

https://github.com/vivekjustthink/WeakConcurrentHashMap

Alkış !!


Neden cleanMap()beklenen sürenin yarısını çalıştırıyorsunuz ?
EliuX

Bcoz, tuşların süresinin dolmasını (kaldırılmasını) sağlar ve ipliğin aşırı döngüden kaçınmasını sağlar.
Vivek

@Vivek ancak bu uygulama ile zaten süresi dolmuş ancak önbellekte mevcut olan maksimum (expiryInMillis / 2) giriş sayısı olabilir. İplik expiryInMillis / 2 dönemden sonra girişlerini siler gibi
rishi007bansod

19

Zaman aşımına uğrayan bir karma harita uygulamamı deneyebilirsiniz . Bu uygulama, süresi dolmuş girdileri kaldırmak için iş parçacıklarını kullanmaz, bunun yerine her işlemde otomatik olarak temizlenen DelayQueue kullanır .


Guava'nın versiyonunu daha çok seviyorum, ancak resme bütünlük katmak için +1
Sean Patrick Floyd

@ piero86 yöntem expireKey (ExpiringKey <K> delayedKey) içinde delayQueue.poll () çağrısının yanlış olduğunu söyleyebilirim. Daha sonra temizleme () - bir sızıntıda kullanılamayan keyfi bir ExpiringKey kaybedebilirsiniz.
Stefan Zobel

1
Başka bir sorun: Aynı anahtarı farklı yaşam süreleri ile iki kez koyamazsınız. A) (1, 1, shortLived) ifadesini koyduktan sonra, b) (1, 2, longLived) ifadesini koyduktan sonra, longLived ms ne olursa olsun, shortLived ms sonrasında Map girişi silinecektir.
Stefan Zobel

Anlayışınız için teşekkür ederim. Bu sorunları özette yorumlar olarak bildirir misiniz, lütfen?
pcan

Önerilerinize göre düzeltildi. Teşekkürler.
pcan

19

Apache Commons'ın Harita'nın girişlerini sona erdirmesi için dekoratörü vardır: PassiveExpiringMap önbelleklerinden daha basit.

PS dikkatli olun, senkronize değil.


1
Çok basit, ancak bir girişe eriştikten sonra son kullanma süresini kontrol eder.
Badie

Gereğince Javadoc : tüm harita içeriğini erişim gerektirir yöntemleri çağırmak zaman (yani containsKey (Object), entrySet (), vb) bu dekoratör dolan tüm girdileri kaldırır önce aslında çağırmayı tamamlamaya.
NS du Toit

Bu kütüphanenin en son sürümünün (Apache commons commons-collections4) ne olduğunu görmek istiyorsanız, burada mvnrepository'deki ilgili kütüphaneye bir bağlantı
NS du Toit

3

Ehcache, istediğiniz şey için aşırıya kaçıyor gibi görünüyor, ancak harici yapılandırma dosyalarına ihtiyaç duymadığını unutmayın.

Yapılandırmayı bildirici bir yapılandırma dosyalarına taşımak genellikle iyi bir fikirdir (bu nedenle yeni bir kurulum farklı bir süre sonu gerektirdiğinde yeniden derlemenize gerek yoktur), ancak gerekli değildir, yine de programlı olarak yapılandırabilirsiniz. http://www.ehcache.org/documentation/user-guide/configuration


2

Google koleksiyonları (guava), zaman sınırını (süre sonu için) ayarlayabileceğiniz MapMaker'a sahiptir ve seçtiğiniz örnekleri oluşturmak için bir fabrika yöntemini kullanarak seçtiğiniz yumuşak veya zayıf referansı kullanabilirsiniz.



2

Herhangi birinin basit bir şeye ihtiyacı varsa, takip etmek basit bir anahtar süresi doludur. Haritaya kolayca dönüştürülebilir.

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
Bu, tek iş parçacıklı bir durum için iyidir, ancak eşzamanlı bir durumda sefil bir şekilde kırılacaktır.
Sean Patrick Floyd

@SeanPatrickFloyd LinkedHashMap'in kendisi gibi mi demek istiyorsun ?! LinkedHashMap, HashMap gibi "harici olarak senkronize edilmelidir" diyorsunuz.
palindrom

evet, tüm bunlar gibi, ama Guava'nın önbelleğinin aksine (kabul edilen cevap)
Sean Patrick Floyd

Ayrıca, System.nanoTime()System.currentTimeMillis (), sistem saatine bağlı olduğu ve sürekli olmayabileceği için tutarlı olmadığından, zaman farklarını hesaplamak için kullanmayı düşünün .
Ercksen

2

Tipik olarak, bir önbellek nesneleri bir süre saklamalı ve bir süre sonra ortaya çıkarmalıdır. Ne bir nesneyi tutmak için iyi bir zaman kullanım durumuna göre değişir. Bu şeyin basit olmasını istedim, konu veya programlayıcı yok. Bu yaklaşım benim için işe yarıyor. SoftReferenceNesnelerin aksine , nesnelerin minimum süre kullanılabilir olması garanti edilir. Ancak, güneş kırmızı bir deve dönüşene kadar bellekte kalmayın .

Kullanım örneği olarak, son zamanlarda bir talebin yapılıp yapılmadığını kontrol edebilecek yavaş yanıt veren bir sistem düşünün ve bu durumda, telaşlı bir kullanıcı düğmeye birkaç kez vursa bile, istenen eylemi iki kez gerçekleştirmemeyi düşünün. Ancak, aynı eylem bir süre sonra talep edilirse, tekrar yapılacaktır.

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

güzel, teşekkür ederim
bigbadmouse

1
HashMap, yarış koşulları, map.put işlemi veya haritanın yeniden boyutlandırılması nedeniyle iş parçacığı için güvenli değildir, verilerin bozulmasına neden olabilir. Buraya bakın: mailinator.blogspot.com/2009/06/beautiful-race-condition.html
Eugene Maysyuk

Bu doğru. Aslında, çoğu Java sınıfı iş parçacığı için güvenli değildir. İş parçacığı güvenliğine ihtiyacınız varsa, gereksinimi karşılayıp karşılamadığını görmek için tasarımınızın etkilenen her sınıfını kontrol etmeniz gerekir.
Matthias Ronge

1

Guava önbelleğinin uygulanması kolaydır. Tamamen yazı okudum ve aşağıda çalışmamın anahtarını veriyor.

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

              }

Referans: guava önbellek örneği


1
lütfen şimdi çalışmıyor olarak bağlantıyı güncelleyin
smaiakov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.