Tolong jangan katakan EHCache atau OSCache, dll. Asumsikan untuk keperluan pertanyaan ini bahwa saya ingin menerapkan sendiri menggunakan hanya SDK (belajar sambil melakukan). Mengingat bahwa cache akan digunakan dalam lingkungan multithreaded, struktur data apa yang akan Anda gunakan? Saya sudah menerapkan satu menggunakan LinkedHashMap dan Koleksi # sinkronisasi , tapi saya ingin tahu apakah ada salah satu koleksi bersamaan akan menjadi kandidat yang lebih baik.
UPDATE: Saya baru saja membaca Yegge terbaru ketika saya menemukan nugget ini:
Jika Anda membutuhkan akses waktu konstan dan ingin mempertahankan urutan penyisipan, Anda tidak dapat melakukan lebih baik daripada LinkedHashMap, struktur data yang benar-benar luar biasa. Satu-satunya cara itu mungkin bisa lebih indah adalah jika ada versi bersamaan. Tapi sayang sekali.
Saya memikirkan hal yang persis sama sebelum saya pergi dengan implementasi LinkedHashMap
+ yang Collections#synchronizedMap
saya sebutkan di atas. Senang mengetahui bahwa saya tidak hanya mengabaikan sesuatu.
Berdasarkan jawaban sejauh ini, sepertinya taruhan terbaik saya untuk LRU yang sangat konkuren adalah dengan memperpanjang ConcurrentHashMap menggunakan beberapa logika yang sama yang LinkedHashMap
digunakan.
sumber
O(1)
versi yang diperlukan: stackoverflow.com/questions/23772102/…Jawaban:
Saya suka banyak saran ini, tetapi untuk sekarang saya pikir saya akan tetap dengan
LinkedHashMap
+Collections.synchronizedMap
. Jika saya mengunjungi kembali ini di masa depan, saya mungkin akan berusaha memperluasConcurrentHashMap
dengan cara yang samaLinkedHashMap
meluasHashMap
.MEMPERBARUI:
Berdasarkan permintaan, inilah inti dari implementasi saya saat ini.
sumber
LinkedHashMap
secara eksplisit mendukung metode ini untuk membuat implementasi LRU.Jika saya melakukan ini lagi dari awal hari ini, saya akan menggunakan Guava
CacheBuilder
.sumber
Ini babak dua.
Babak pertama adalah apa yang saya hasilkan kemudian saya membaca kembali komentar dengan domain yang sedikit lebih tertanam di kepala saya.
Jadi di sini adalah versi paling sederhana dengan tes unit yang menunjukkan itu berfungsi berdasarkan beberapa versi lainnya.
Pertama versi non-konkuren:
Bendera yang sebenarnya akan melacak akses dari mendapat dan menempatkan. Lihat JavaDocs. RemoveEdelstEntry tanpa flag yang sebenarnya ke konstruktor hanya akan mengimplementasikan cache FIFO (lihat catatan di bawah tentang FIFO dan removeEldestEntry).
Berikut adalah tes yang membuktikannya berfungsi sebagai cache LRU:
Sekarang untuk versi bersamaan ...
paket org.boon.cache;
Anda dapat melihat mengapa saya membahas versi non-konkuren terlebih dahulu. Upaya di atas untuk membuat beberapa garis untuk mengurangi pertikaian kunci. Jadi kita hash kunci dan kemudian mencari hash untuk menemukan cache yang sebenarnya. Ini membuat ukuran batas lebih dari saran / tebakan kasar dalam jumlah kesalahan yang wajar tergantung pada seberapa baik penyebaran algoritma hash kunci Anda.
Berikut adalah tes untuk menunjukkan bahwa versi bersamaan mungkin berfungsi. :) (Tes di bawah api akan menjadi cara nyata).
Ini adalah posting terakhir .. Posting pertama yang saya hapus karena itu adalah LFU bukan cache LRU.
Saya pikir saya akan mencoba ini lagi. Saya mencoba untuk membuat versi paling sederhana dari cache LRU menggunakan standar JDK tanpa implementasi terlalu banyak.
Inilah yang saya pikirkan. Upaya pertama saya adalah sedikit bencana ketika saya menerapkan LFU dan bukannya LRU, dan kemudian saya menambahkan FIFO, dan dukungan LRU untuk itu ... dan kemudian saya menyadari itu menjadi monster. Kemudian saya mulai berbicara dengan teman saya John yang hampir tidak tertarik, dan kemudian saya jelaskan panjang lebar bagaimana saya menerapkan LFU, LRU dan FIFO dan bagaimana Anda bisa mengubahnya dengan arg ENUM sederhana, dan kemudian saya menyadari bahwa semua yang saya inginkan adalah LRU sederhana. Jadi abaikan pos sebelumnya dari saya, dan beri tahu saya jika Anda ingin melihat cache LRU / LFU / FIFO yang dapat dialihkan melalui enum ... tidak? Ok .. ini dia.
LRU yang paling sederhana dengan JDK. Saya menerapkan versi bersamaan dan versi tidak bersamaan.
Saya membuat antarmuka umum (ini minimalis sehingga kemungkinan kehilangan beberapa fitur yang Anda inginkan tetapi berfungsi untuk kasus penggunaan saya, tetapi biarkan jika Anda ingin melihat fitur XYZ beri tahu saya ... Saya hidup untuk menulis kode.) .
Anda mungkin bertanya-tanya apa itu getSilent . Saya menggunakan ini untuk pengujian. getSilent tidak mengubah skor LRU dari suatu item.
Pertama yang tidak bersamaan ....
The queue.removeFirstOccurrence adalah operasi berpotensi mahal jika Anda memiliki cache yang besar. Orang bisa mengambil LinkedList sebagai contoh dan menambahkan peta hash reverse lookup dari elemen ke node untuk membuat operasi penghapusan BANYAK LEBIH CEPAT dan lebih konsisten. Saya mulai juga, tetapi kemudian menyadari bahwa saya tidak membutuhkannya. Tapi mungkin...
Ketika put dipanggil, kunci akan ditambahkan ke antrian. Ketika mendapatkan disebut, kunci akan dihapus dan kembali ditambahkan ke bagian atas dari antrian.
Jika cache Anda kecil dan membuat item mahal maka ini harus menjadi cache yang baik. Jika cache Anda benar-benar besar, maka pencarian linier bisa menjadi leher botol terutama jika Anda tidak memiliki area cache yang panas. Semakin banyak hot spot, semakin cepat pencarian linier karena item panas selalu berada di bagian atas pencarian linear. Ngomong-ngomong ... apa yang diperlukan agar ini berjalan lebih cepat adalah menulis LinkedList lain yang memiliki operasi hapus yang memiliki elemen terbalik ke pencarian simpul untuk dihapus, kemudian menghapusnya akan secepat menghapus kunci dari peta hash.
Jika Anda memiliki cache di bawah 1.000 item, ini akan bekerja dengan baik.
Berikut ini adalah tes sederhana untuk menunjukkan operasinya dalam tindakan.
Cache LRU terakhir adalah utas tunggal, dan tolong jangan membungkusnya dengan sinkronisasi apa pun ....
Ini adalah tikaman pada versi bersamaan.
Perbedaan utama adalah penggunaan ConcurrentHashMap, bukan HashMap, dan penggunaan Lock (saya bisa lolos dengan disinkronkan, tapi ...).
Saya belum mengujinya di bawah api, tetapi sepertinya cache LRU sederhana yang mungkin berhasil di 80% kasus penggunaan di mana Anda memerlukan peta LRU sederhana.
Saya menyambut umpan balik, kecuali mengapa Anda tidak menggunakan perpustakaan a, b, atau c. Alasan saya tidak selalu menggunakan perpustakaan adalah karena saya tidak selalu ingin setiap file perang menjadi 80MB, dan saya menulis perpustakaan jadi saya cenderung membuat lib plug-mampu dengan solusi yang cukup bagus di tempat dan seseorang dapat memasang -di penyedia cache lain jika mereka suka. :) Saya tidak pernah tahu kapan seseorang mungkin membutuhkan Guava atau ehcache atau sesuatu yang lain yang saya tidak ingin sertakan, tetapi jika saya membuat caching plug-mampu, saya tidak akan mengecualikan mereka juga.
Pengurangan ketergantungan memiliki imbalannya sendiri. Saya suka mendapatkan umpan balik tentang cara menjadikan ini lebih sederhana atau lebih cepat atau keduanya.
Juga jika ada yang tahu siap untuk pergi ....
Ok .. Saya tahu apa yang Anda pikirkan ... Mengapa dia tidak hanya menggunakan entri removeEldest dari LinkedHashMap, dan saya harus tetapi .... tapi .. tapi .. Itu akan menjadi FIFO bukan LRU dan kami mencoba menerapkan LRU.
Tes ini gagal untuk kode di atas ...
Jadi di sini adalah cache FIFO yang cepat dan kotor menggunakan removeEldestEntry.
FIFO cepat. Tidak mencari-cari. Anda dapat mem-depan FIFO di depan LRU dan itu akan menangani sebagian besar entri panas dengan cukup baik. LRU yang lebih baik akan membutuhkan elemen pembalik untuk fitur Node.
Ngomong-ngomong ... sekarang saya menulis beberapa kode, biarkan saya memeriksa jawaban lain dan melihat apa yang saya lewatkan ... pertama kali saya memindai mereka.
sumber
LinkedHashMap
adalah O (1), tetapi membutuhkan sinkronisasi. Tidak perlu menemukan kembali roda di sana.2 opsi untuk meningkatkan konkurensi:
1. Buat beberapa
LinkedHashMap
, dan hash ke mereka: Contoh:LinkedHashMap[4], index 0, 1, 2, 3
. Pada tombol dokey%4
(ataubinary OR
on[key, 3]
) untuk memilih peta mana yang akan dilakukan put / get / remove.2. Anda dapat melakukan 'hampir' LRU dengan memperluas
ConcurrentHashMap
, dan memiliki struktur peta hash yang terhubung di setiap wilayah di dalamnya. Penguncian akan terjadi lebih rinci daripadaLinkedHashMap
yang disinkronkan. Dibutuhkan satuput
atauputIfAbsent
hanya kunci di kepala dan ekor daftar (per wilayah). Pada menghapus atau mendapatkan seluruh wilayah harus dikunci. Saya ingin tahu apakah daftar yang ditautkan dari Atom dapat membantu di sini - mungkin demikian bagi kepala daftar. Mungkin lebih.Struktur tidak akan menyimpan pesanan total, tetapi hanya pesanan per wilayah. Selama jumlah entri jauh lebih besar dari jumlah wilayah, ini cukup baik untuk sebagian besar cache. Setiap daerah harus memiliki jumlah entri sendiri, ini akan digunakan daripada jumlah global untuk pemicu penggusuran. Jumlah standar wilayah dalam a
ConcurrentHashMap
adalah 16, yang banyak untuk sebagian besar server saat ini.akan lebih mudah untuk menulis dan lebih cepat di bawah konkurensi moderat.
akan lebih sulit untuk menulis tetapi skalanya jauh lebih baik pada konkurensi yang sangat tinggi. Akan lebih lambat untuk akses normal (sama seperti
ConcurrentHashMap
lebih lambat daripada diHashMap
mana tidak ada konkurensi)sumber
Ada dua implementasi open source.
Apache Solr memiliki ConcurrentLRUCache: https://lucene.apache.org/solr/3_6_1/org/apache/solr/util/ConcurrentLRUCache.html
Ada proyek sumber terbuka untuk ConcurrentLinkedHashMap: http://code.google.com/p/concurrentlinkedhashmap/
sumber
ConcurrentLinkedHashMap
menarik. Ia mengklaim telah digulungMapMaker
dari Guava, tetapi saya tidak menemukannya di dokumen. Adakah yang tahu apa yang terjadi dengan upaya itu?Saya akan mempertimbangkan menggunakan java.util.concurrent.PriorityBlockingQueue , dengan prioritas ditentukan oleh penghitung "numberOfUses" di setiap elemen. Saya akan sangat, sangat berhati - hati untuk mendapatkan semua sinkronisasi saya yang benar, karena penghitung "numberOfUses" menyiratkan bahwa elemen tidak dapat berubah.
Objek elemen akan menjadi pembungkus untuk objek dalam cache:
sumber
Semoga ini membantu .
sumber
LRU Cache dapat diimplementasikan menggunakan ConcurrentLinkedQueue dan ConcurrentHashMap yang dapat digunakan dalam skenario multithreading juga. Kepala antrian adalah elemen yang telah lama berada di antrian. Ekor antrian adalah elemen yang telah berada di antrian dalam waktu singkat. Saat ada elemen di Peta, kita bisa menghapusnya dari LinkedQueue dan memasukkannya di bagian ekor.
sumber
put
.Inilah implementasi saya untuk LRU. Saya telah menggunakan PriorityQueue, yang pada dasarnya berfungsi sebagai FIFO dan bukan threadsafe. Comparator Bekas berdasarkan pembuatan waktu halaman dan berdasarkan pada melakukan urutan halaman untuk waktu yang terakhir digunakan.
Halaman untuk dipertimbangkan: 2, 1, 0, 2, 8, 2, 4
Halaman yang ditambahkan ke dalam cache adalah: 2
Halaman yang ditambahkan ke dalam cache adalah: 1
Halaman yang ditambahkan ke dalam cache adalah: 0
Halaman: 2 sudah ada dalam cache. Waktu diakses terakhir yang diperbarui
Page Fault, HALAMAN: 1, Diganti dengan HALAMAN: 8
Halaman yang ditambahkan ke dalam cache adalah: 8
Halaman: 2 sudah ada dalam cache. Waktu diakses terakhir yang diperbarui
Page Fault, HALAMAN: 0, Diganti dengan HALAMAN: 4
Halaman yang ditambahkan ke dalam cache adalah: 4
KELUARAN
Halaman LRUCache
-------------
PageName: 8, PageCreationTime: 1365957019974
PageName: 2, PageCreationTime: 1365957020074
PageName: 4, PageCreationTime: 1365957020174
masukkan kode di sini
sumber
Berikut ini adalah implementasi cache LRU bersamaan yang terbukti paling berhasil tanpa blok yang disinkronkan:
}
sumber
Ini adalah cache LRU yang saya gunakan, yang merangkum LinkedHashMap dan menangani konkurensi dengan kunci sinkronisasi sederhana yang menjaga tempat-tempat menarik. Itu "menyentuh" elemen seperti yang digunakan sehingga mereka menjadi elemen "segar" lagi, sehingga itu sebenarnya LRU. Saya juga memiliki persyaratan elemen saya memiliki umur minimum, yang Anda juga bisa anggap sebagai "waktu idle maksimum" diizinkan, maka Anda siap untuk penggusuran.
Namun, saya setuju dengan kesimpulan Hank dan menerima jawaban - jika saya memulai ini lagi hari ini, saya akan mengecek Guava
CacheBuilder
.sumber
Nah untuk cache, Anda biasanya akan mencari beberapa bagian data melalui objek proxy, (URL, String ....) jadi dari segi antarmuka Anda akan menginginkan peta. tetapi untuk menendang keluar Anda ingin struktur seperti antrian. Secara internal saya akan mempertahankan dua struktur data, Prioritas-Antrian dan HashMap. Inilah implementasi yang harus dapat melakukan segalanya dalam waktu O (1).
Inilah kelas yang saya ikuti dengan cepat:
Begini cara kerjanya. Kunci disimpan dalam daftar tertaut dengan kunci terlama di bagian depan daftar (kunci baru pergi ke belakang) sehingga ketika Anda perlu 'mengeluarkan' sesuatu, Anda cukup mengeluarkannya dari depan antrian dan kemudian gunakan tombol untuk hapus nilai dari peta. Ketika sebuah item direferensikan, Anda mengambil ValueHolder dari peta dan kemudian menggunakan variabel queuelocation untuk menghapus kunci dari lokasi saat ini dalam antrian dan kemudian meletakkannya di bagian belakang antrian (sekarang yang paling baru digunakan). Menambahkan banyak hal hampir sama.
Saya yakin ada banyak kesalahan di sini dan saya belum menerapkan sinkronisasi apa pun. tetapi kelas ini akan memberikan O (1) menambah cache, O (1) menghapus item lama, dan O (1) mengambil item cache. Bahkan sinkronisasi sepele (hanya menyinkronkan setiap metode publik) masih akan memiliki sedikit pertikaian kunci karena waktu berjalan. Jika ada yang punya trik sinkronisasi pintar saya akan sangat tertarik. Selain itu, saya yakin ada beberapa optimasi tambahan yang bisa Anda terapkan menggunakan variabel maksimum sehubungan dengan peta.
sumber
LinkedHashMap
+Collections.synchronizedMap()
?Lihatlah ConcurrentSkipListMap . Seharusnya memberi Anda log (n) waktu untuk menguji dan menghapus elemen jika sudah ada dalam cache, dan waktu yang konstan untuk menambahkannya kembali.
Anda hanya perlu beberapa counter dll dan elemen pembungkus untuk memaksa memesan pesanan LRU dan memastikan barang-barang terbaru dibuang ketika cache penuh.
sumber
ConcurrentSkipListMap
memberikan manfaat kemudahan implementasiConcurrentHashMap
, atau itu hanya kasus menghindari kasus patologis?ConcurrentSkipListMap
implementasinya, saya akan membuat implementasi baruMap
antarmuka yang mendelegasikanConcurrentSkipListMap
dan melakukan semacam pembungkus sehingga jenis kunci sewenang-wenang dibungkus dalam jenis yang mudah disortir berdasarkan akses terakhir?Inilah implementasi singkat saya, mohon kritik atau perbaiki!
sumber
Inilah implementasi saya sendiri untuk masalah ini
simplelrucache menyediakan caching LRU non-didistribusikan threadsafe, sangat sederhana, dengan dukungan TTL. Ini menyediakan dua implementasi:
Anda dapat menemukannya di sini: http://code.google.com/p/simplelrucache/
sumber
Cara terbaik untuk mencapainya adalah dengan menggunakan LinkedHashMap yang mempertahankan urutan elemen. Berikut ini adalah contoh kode:
}
sumber
Saya mencari cache LRU yang lebih baik menggunakan kode Java. Apakah mungkin bagi Anda untuk membagikan kode cache Java LRU Anda menggunakan
LinkedHashMap
danCollections#synchronizedMap
? Saat ini saya menggunakanLRUMap implements Map
dan kode berfungsi dengan baik, tapi saya sedang melakukanArrayIndexOutofBoundException
pengujian beban menggunakan 500 pengguna pada metode di bawah ini. Metode ini memindahkan objek terbaru ke depan antrian.get(Object key)
danput(Object key, Object value)
metode memanggil metode di atasmoveToFront
.sumber
Ingin menambahkan komentar pada jawaban yang diberikan oleh Hank tetapi bagaimana saya tidak bisa - tolong perlakukan itu sebagai komentar
LinkedHashMap mempertahankan urutan akses juga berdasarkan pada parameter yang diteruskan dalam konstruktornya. Ini membuat daftar dua kali lipat untuk menjaga ketertiban (Lihat LinkedHashMap.Entry)
@Pacerier memang benar bahwa LinkedHashMap menyimpan urutan yang sama saat iterasi jika elemen ditambahkan lagi tetapi itu hanya dalam kasus mode urutan penyisipan.
ini adalah apa yang saya temukan di dokumen java dari objek LinkedHashMap.Entry
metode ini menangani pemindahan elemen yang baru saja diakses ke akhir daftar. Jadi semuanya, LinkedHashMap adalah struktur data terbaik untuk mengimplementasikan LRUCache.
sumber
Pemikiran lain dan bahkan implementasi sederhana menggunakan koleksi Java LinkedHashMap.
LinkedHashMap menyediakan metode removeEldestEntry dan yang dapat ditimpa dengan cara yang disebutkan dalam contoh. Secara default implementasi dari struktur pengumpulan ini salah. Jika benar dan ukuran struktur ini melampaui kapasitas awal maka elemen tertua atau lebih tua akan dihapus.
Kami dapat memiliki pageno dan konten halaman dalam kasus saya pageno adalah integer dan pagecontent saya menyimpan string nilai halaman.
Hasil dari eksekusi kode di atas adalah sebagai berikut:
sumber
Mengikuti konsep @sanjanab (tetapi setelah perbaikan), saya membuat versi LRUCache yang menyediakan juga Konsumen yang memungkinkan untuk melakukan sesuatu dengan item yang dihapus jika diperlukan.
sumber
Android menawarkan implementasi LRU Cache . The kode bersih dan mudah.
sumber