Peta / cache berbasis waktu Java dengan kunci kedaluwarsa [ditutup]

253

Apakah ada di antara Anda yang tahu tentang Java Map atau penyimpanan data standar serupa yang secara otomatis membersihkan entri setelah batas waktu tertentu? Ini berarti penuaan, di mana entri yang sudah kadaluwarsa “usang” secara otomatis.

Lebih disukai di perpustakaan sumber terbuka yang dapat diakses melalui Maven?

Saya tahu cara untuk menerapkan fungsi sendiri dan telah melakukannya beberapa kali di masa lalu, jadi saya tidak meminta saran dalam hal itu, tetapi untuk petunjuk penerapan referensi yang baik.

Solusi berbasis WeakReference seperti WeakHashMap bukan pilihan, karena kunci saya cenderung bukan string yang diinternir dan saya ingin batas waktu yang dapat dikonfigurasi yang tidak bergantung pada pengumpul sampah.

Ehcache juga merupakan opsi yang tidak ingin saya andalkan karena memerlukan file konfigurasi eksternal. Saya mencari solusi hanya kode.

Sean Patrick Floyd
sumber
1
Lihat Google Collections (sekarang disebut Guava). Ini memiliki peta yang dapat menghentikan entri secara otomatis.
dty
3
Betapa aneh bahwa pertanyaan dengan 253 upvote dan 176k dilihat - yang menempati peringkat super tinggi di mesin pencari untuk topik ini - telah ditutup karena tidak memenuhi pedoman
Brian

Jawaban:

320

Iya. Google Collections, atau Jambu seperti namanya sekarang memiliki sesuatu yang disebut MapMaker yang dapat melakukan hal itu.

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

Memperbarui:

Pada jambu 10.0 (dirilis 28 September 2011) banyak dari metode MapMaker ini telah ditinggalkan demi CacheBuilder baru :

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);
          }
        });
Shervin Asgari
sumber
5
Luar biasa, saya tahu Guava punya jawaban tetapi saya tidak bisa menemukannya! (+1)
Sean Patrick Floyd
12
Mulai dari v10, Anda harus menggunakan CacheBuilder sebagai gantinya ( guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/… ) karena kedaluwarsa dll telah ditinggalkan di
MapMaker
49
Peringatan ! Menggunakan weakKeys()menyiratkan bahwa kunci dibandingkan menggunakan semantik ==, bukan equals(). Saya kehilangan 30 menit mencari tahu mengapa cache String-keyed saya tidak berfungsi :)
Laurent Grégoire
3
Teman-teman, hal yang @Laurent sebutkan weakKeys()penting. weakKeys()tidak diperlukan 90% dari waktu.
Manu Manjunath
3
@ ShervinAsgari demi pemula (termasuk saya sendiri), dapatkah Anda mengganti contoh jambu yang diperbarui ke yang menggunakan Cache alih-alih MemuatCache? Itu akan cocok dengan pertanyaan yang lebih baik (karena LoadingCache memiliki fitur yang melebihi peta dengan entri yang kedaluwarsa dan jauh lebih rumit untuk dibuat) lihat github.com/google/guava/wiki/CachesExplained#from-a-callable
Jeutnarg
29

Ini adalah contoh implementasi yang saya lakukan untuk persyaratan dan konkurensi yang sama berfungsi dengan baik. Mungkin bermanfaat bagi seseorang.

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 Link (Dengan Implementasi Pendengar)

https://github.com/vivekjustthink/WeakConcurrentHashMap

Bersulang!!

Vivek
sumber
Mengapa Anda melakukan cleanMap()setengah dari waktu yang ditentukan?
EliuX
Bcoz itu memastikan kunci telah kedaluwarsa (dihapus) dan menghindari utas dari perulangan ekstrim.
Vivek
@Vivek tetapi dengan implementasi ini akan ada jumlah entri maksimal (expiryInMillis / 2) yang sudah kadaluwarsa tetapi masih ada dalam cache. Sebagai utas, menghapus entri setelah kedaluwarsa / 2 periode
rishi007bansod
19

Anda dapat mencoba implementasi saya dari peta hash yang kedaluwarsa. Implementasi ini tidak menggunakan utas untuk menghapus entri yang kadaluwarsa, melainkan menggunakan DelayQueue yang dibersihkan di setiap operasi secara otomatis.

pcan
sumber
Saya suka versi Guava lebih baik, tetapi +1 untuk menambahkan kelengkapan gambar
Sean Patrick Floyd
@ piero86 Saya akan mengatakan panggilan ke delayQueue.poll () dalam metode expireKey (ExpiringKey <K> menundaKey) salah. Anda dapat kehilangan ExpiringKey yang sewenang-wenang yang nantinya tidak dapat digunakan dalam pembersihan () - kebocoran.
Stefan Zobel
1
Masalah lain: Anda tidak dapat memasukkan kunci yang sama dua kali dengan masa hidup yang berbeda. Setelah a) meletakkan (1, 1, shortLived), lalu b) meletakkan (1, 2, longLived) entri Peta untuk kunci 1 akan hilang setelah ms shortLived, tidak peduli berapa lama longLived adalah.
Stefan Zobel
Terima kasih atas wawasan Anda. Bisakah Anda melaporkan masalah ini sebagai komentar di intinya?
pcan
Diperbaiki sesuai dengan saran Anda. Terima kasih.
pcan
19

Apache Commons memiliki dekorator untuk Peta untuk kedaluwarsa entri: PassiveExpiringMap Ini lebih sederhana daripada cache dari Guava.

PS berhati-hatilah, itu tidak disinkronkan.

Guram Savinov
sumber
1
Ini sederhana, tetapi memeriksa waktu kedaluwarsa hanya setelah Anda mengakses entri.
Badie
Sesuai Javadoc : Ketika menerapkan metode yang melibatkan mengakses seluruh isi peta (yaitu berisi Kunci (Objek), entriSet (), dll.) Dekorator ini menghapus semua entri yang kadaluwarsa sebelum benar-benar menyelesaikan doa.
NS du Toit
Jika Anda ingin melihat apa versi terbaru dari perpustakaan ini (Apache commons commons-collections4) di sini adalah tautan ke perpustakaan yang relevan di mvnrepository
NS du Toit
3

Kedengarannya seperti ehcache terlalu banyak untuk apa yang Anda inginkan, tetapi perhatikan bahwa itu tidak memerlukan file konfigurasi eksternal.

Pada umumnya ide yang baik untuk memindahkan konfigurasi ke file konfigurasi deklaratif (jadi Anda tidak perlu mengkompilasi ulang ketika instalasi baru memerlukan waktu kedaluwarsa yang berbeda), tetapi sama sekali tidak diperlukan, Anda masih dapat mengonfigurasinya secara terprogram. http://www.ehcache.org/documentation/user-guide/configuration

dan carter
sumber
2

Koleksi Google (jambu) memiliki MapMaker di mana Anda dapat mengatur batas waktu (untuk kedaluwarsa) dan Anda dapat menggunakan referensi lunak atau lemah saat Anda memilih menggunakan metode pabrik untuk membuat contoh pilihan Anda.

Emil
sumber
2

Jika ada yang membutuhkan hal yang sederhana, mengikuti adalah himpunan kunci yang kedaluwarsa. Mungkin mudah dikonversi ke peta.

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();
        }
    }
}
palindrom
sumber
2
Ini bagus untuk situasi single-thread, tetapi akan rusak secara menyedihkan dalam situasi bersamaan.
Sean Patrick Floyd
@SeanPatrickFloyd maksudmu seperti LinkedHashMap sendiri ?! "itu harus disinkronkan secara eksternal" seperti LinkedHashMap, HashMap ... sebut saja.
palindrom
ya, seperti semua itu, tetapi tidak seperti cache Guava (jawaban yang diterima)
Sean Patrick Floyd
Juga, pertimbangkan System.nanoTime()untuk menggunakan perbedaan waktu komputasi karena System.currentTimeMillis () tidak konsisten karena tergantung pada waktu sistem dan mungkin tidak berkelanjutan.
Ercksen
2

Biasanya, cache harus menyimpan objek di sekitar waktu tertentu dan akan mengeksposnya beberapa saat kemudian. Apa waktu yang baik untuk mengadakan suatu objek tergantung pada kasus penggunaan. Saya ingin hal ini menjadi sederhana, tanpa utas atau penjadwal. Pendekatan ini bekerja untuk saya. Tidak seperti SoftReferences, objek dijamin akan tersedia dalam jumlah waktu minimum. Namun, jangan tinggal di dalam memori sampai matahari berubah menjadi raksasa merah .

Sebagai contoh penggunaan, pikirkan sistem yang merespons secara lambat yang harus dapat memeriksa apakah permintaan telah dilakukan baru-baru ini, dan dalam hal ini jangan melakukan tindakan yang diminta dua kali, bahkan jika pengguna yang sibuk menekan tombol beberapa kali. Tetapi, jika tindakan yang sama diminta beberapa waktu kemudian, itu akan dilakukan lagi.

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();
            }
        }
    }
}
Matthias Ronge
sumber
bagus, terima kasih
bigbadmouse
1
HashMap bukan utas yang aman, karena kondisi ras, operasi map.put, atau pengubahan ukuran peta dapat menyebabkan korupsi data. Lihat di sini: mailinator.blogspot.com/2009/06/beautiful-race-condition.html
Eugene Maysyuk
Itu benar. Memang, sebagian besar kelas Java tidak aman untuk thread. Jika Anda membutuhkan keamanan utas, Anda perlu memeriksa setiap kelas desain yang terpengaruh untuk melihat apakah memenuhi persyaratan.
Matthias Ronge
1

Cache jambu biji mudah diimplementasikan. Kami dapat kedaluwarsa pada basis waktu menggunakan cache jambu biji. Saya telah membaca sepenuhnya posting dan di bawah ini memberikan kunci studi saya.

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

              }

Referensi: contoh cache jambu biji

Anuj Dhiman
sumber
1
perbarui tautan karena tidak berfungsi sekarang
smaiakov