Cache Jambu Batu

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan melihat pelaksanaan Guava Cache - penggunaan asas, dasar pengusiran, penyegaran cache dan beberapa operasi pukal yang menarik.

Akhirnya, kita akan melihat penggunaan pemberitahuan penghapusan yang dapat dihantar oleh cache.

2. Cara Menggunakan Jambu Batu

Mari kita mulakan dengan contoh mudah - mari kita simpan bentuk huruf besar contoh String .

Pertama, kami akan membuat CacheLoader - digunakan untuk mengira nilai yang tersimpan dalam cache. Dari ini, kami akan menggunakan CacheBuilder yang berguna untuk membina cache kami menggunakan spesifikasi yang diberikan:

@Test public void whenCacheMiss_thenValueIsComputed() { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder().build(loader); assertEquals(0, cache.size()); assertEquals("HELLO", cache.getUnchecked("hello")); assertEquals(1, cache.size()); }

Perhatikan bagaimana tidak ada nilai dalam cache untuk kunci "hello" kami - dan nilainya dihitung dan di-cache.

Perhatikan juga bahawa kami menggunakan operasi getUnchecked () - ini mengira dan memuatkan nilai ke dalam cache jika belum ada.

3. Dasar Pengusiran

Setiap cache perlu membuang nilai pada satu ketika. Mari kita bincangkan mekanisme mengusir nilai dari cache - menggunakan kriteria yang berbeza.

3.1. Pengusiran mengikut Saiz

Kita boleh mengehadkan ukuran cache kita menggunakan maksimumSize () . Sekiranya cache mencapai had, item tertua akan diusir.

Dalam kod berikut, kami menghadkan ukuran cache kepada 3 rekod:

@Test public void whenCacheReachMaxSize_thenEviction() { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder().maximumSize(3).build(loader); cache.getUnchecked("first"); cache.getUnchecked("second"); cache.getUnchecked("third"); cache.getUnchecked("forth"); assertEquals(3, cache.size()); assertNull(cache.getIfPresent("first")); assertEquals("FORTH", cache.getIfPresent("forth")); }

3.2. Pengusiran Mengikut Berat

Kami juga dapat membatasi ukuran cache menggunakan fungsi berat khusus. Dalam kod berikut, kami menggunakan panjang sebagai fungsi berat tersuai kami:

@Test public void whenCacheReachMaxWeight_thenEviction() { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; Weigher weighByLength; weighByLength = new Weigher() { @Override public int weigh(String key, String value) { return value.length(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder() .maximumWeight(16) .weigher(weighByLength) .build(loader); cache.getUnchecked("first"); cache.getUnchecked("second"); cache.getUnchecked("third"); cache.getUnchecked("last"); assertEquals(3, cache.size()); assertNull(cache.getIfPresent("first")); assertEquals("LAST", cache.getIfPresent("last")); }

Catatan: Cache mungkin membuang lebih dari satu rekod untuk meninggalkan ruang untuk yang baru.

3.3. Pengusiran mengikut Masa

Selain menggunakan ukuran untuk mengusir catatan lama, kita dapat menggunakan waktu. Dalam contoh berikut, kami menyesuaikan cache kami untuk membuang rekod yang tidak digunakan selama 2ms :

@Test public void whenEntryIdle_thenEviction() throws InterruptedException { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder() .expireAfterAccess(2,TimeUnit.MILLISECONDS) .build(loader); cache.getUnchecked("hello"); assertEquals(1, cache.size()); cache.getUnchecked("hello"); Thread.sleep(300); cache.getUnchecked("test"); assertEquals(1, cache.size()); assertNull(cache.getIfPresent("hello")); }

Kami juga dapat mengusir rekod berdasarkan jumlah masa hidup mereka . Dalam contoh berikut, cache akan membuang rekod setelah 2ms disimpan:

@Test public void whenEntryLiveTimeExpire_thenEviction() throws InterruptedException { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder() .expireAfterWrite(2,TimeUnit.MILLISECONDS) .build(loader); cache.getUnchecked("hello"); assertEquals(1, cache.size()); Thread.sleep(300); cache.getUnchecked("test"); assertEquals(1, cache.size()); assertNull(cache.getIfPresent("hello")); }

4. Kekunci Lemah

Seterusnya, mari kita lihat bagaimana membuat kunci cache kita mempunyai rujukan yang lemah - membolehkan pengumpul sampah mengumpulkan kunci cache yang tidak dirujuk di tempat lain.

Secara lalai, kedua-dua kunci dan nilai cache mempunyai rujukan yang kuat tetapi kami dapat menjadikan cache kami menyimpan kunci menggunakan rujukan lemah menggunakan lemahKeys () seperti dalam contoh berikut:

@Test public void whenWeakKeyHasNoRef_thenRemoveFromCache() { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder().weakKeys().build(loader); }

5. Nilai Lembut

Kita boleh membenarkan pengumpul sampah mengumpulkan nilai cache kita dengan menggunakan softValues ​​() seperti dalam contoh berikut:

@Test public void whenSoftValue_thenRemoveFromCache() { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder().softValues().build(loader); }

Catatan: Banyak rujukan lembut boleh mempengaruhi prestasi sistem - lebih disukai menggunakan maksimumSize () .

6. Menangani Nilai batal

Sekarang, mari kita lihat bagaimana menangani nilai nol cache . Secara lalai, Guava Cache akan membuang pengecualian jika anda cuba memuatkan nilai null - kerana tidak masuk akal untuk menyimpan cache nol .

Tetapi jika nilai nol bermaksud sesuatu dalam kod anda, maka anda boleh memanfaatkan kelas Pilihan dengan baik seperti contoh berikut:

@Test public void whenNullValue_thenOptional() { CacheLoader
    
      loader; loader = new CacheLoader
     
      () { @Override public Optional load(String key) { return Optional.fromNullable(getSuffix(key)); } }; LoadingCache
      
        cache; cache = CacheBuilder.newBuilder().build(loader); assertEquals("txt", cache.getUnchecked("text.txt").get()); assertFalse(cache.getUnchecked("hello").isPresent()); } private String getSuffix(final String str) { int lastIndex = str.lastIndexOf('.'); if (lastIndex == -1) { return null; } return str.substring(lastIndex + 1); }
      
     
    

7. Segarkan Cache

Seterusnya, mari kita lihat cara menyegarkan nilai cache kita.

7.1. Penyegaran Manual

Kita boleh memuat semula satu kekunci secara manual dengan bantuan LoadingCache.refresh (kunci).

String value = loadingCache.get("key"); loadingCache.refresh("key");

Ini akan memaksa CacheLoader memuatkan nilai baru untuk kunci tersebut.

Sehingga nilai baru berjaya dimuat, nilai kunci sebelumnya akan dikembalikan oleh get (kunci) .

7.2. Refresh automatik

Kita boleh menggunakan CacheBuilder.refreshAfterWrite (tempoh) untuk menyegarkan nilai cache secara automatik.

@Test public void whenLiveTimeEnd_thenRefresh() { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder() .refreshAfterWrite(1,TimeUnit.MINUTES) .build(loader); }

It's important to understand that refreshAfterWrite(duration) only makes a key eligible for the refresh after the specified duration. The value will actually be refreshed only when a corresponding entry is queried by get(key).

8. Preload the Cache

We can insert multiple records in our cache using putAll() method. In the following example, we add multiple records into our cache using a Map:

@Test public void whenPreloadCache_thenUsePutAll() { CacheLoader loader; loader = new CacheLoader() { @Override public String load(String key) { return key.toUpperCase(); } }; LoadingCache cache; cache = CacheBuilder.newBuilder().build(loader); Map map = new HashMap(); map.put("first", "FIRST"); map.put("second", "SECOND"); cache.putAll(map); assertEquals(2, cache.size()); }

9. RemovalNotification

Sometimes, you need to take some actions when a record is removed from the cache; so, let's discuss RemovalNotification.

We can register a RemovalListener to get notifications of a record being removed. We also have access to the cause of the removal – via the getCause() method.

In the following sample, a RemovalNotification is received when the forth element in the cache because of its size:

@Test public void whenEntryRemovedFromCache_thenNotify() { CacheLoader loader; loader = new CacheLoader() { @Override public String load(final String key) { return key.toUpperCase(); } }; RemovalListener listener; listener = new RemovalListener() { @Override public void onRemoval(RemovalNotification n){ if (n.wasEvicted()) { String cause = n.getCause().name(); assertEquals(RemovalCause.SIZE.toString(),cause); } } }; LoadingCache cache; cache = CacheBuilder.newBuilder() .maximumSize(3) .removalListener(listener) .build(loader); cache.getUnchecked("first"); cache.getUnchecked("second"); cache.getUnchecked("third"); cache.getUnchecked("last"); assertEquals(3, cache.size()); }

10. Notes

Finally, here are a few additional quick notes about the Guava cache implementation:

  • it is thread-safe
  • you can insert values manually into the cache using put(key,value)
  • you can measure your cache performance using CacheStats ( hitRate(), missRate(), ..)

11. Conclusion

Kami menjalani banyak kes penggunaan Guava Cache dalam tutorial ini - dari penggunaan mudah hingga pengusiran elemen, penyegaran dan pramuat cache dan pemberitahuan penghapusan.

Seperti biasa, semua contoh boleh didapati di GitHub.