Guaven-Cache

1. Übersicht

In diesem Tutorial werfen wir einen Blick auf die Implementierung des Guava-Cache - grundlegende Verwendung, Räumungsrichtlinien, Aktualisieren des Caches und einige interessante Massenvorgänge.

Abschließend werfen wir einen Blick auf die Verwendung der Entfernungsbenachrichtigungen, die der Cache senden kann.

2. Verwendung des Guaven-Cache

Beginnen wir mit einem einfachen Beispiel - lassen Sie uns die Großbuchstabenform von String- Instanzen zwischenspeichern.

Zuerst erstellen wir den CacheLoader , mit dem der im Cache gespeicherte Wert berechnet wird. Aus diesem Grund verwenden wir den praktischen CacheBuilder , um unseren Cache unter Verwendung der angegebenen Spezifikationen zu erstellen:

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

Beachten Sie, dass der Cache keinen Wert für unseren "Hallo" -Schlüssel enthält. Daher wird der Wert berechnet und zwischengespeichert.

Beachten Sie auch, dass wir die Operation getUnchecked () verwenden - diese berechnet den Wert und lädt ihn in den Cache, falls er noch nicht vorhanden ist.

3. Räumungsrichtlinien

Jeder Cache muss irgendwann Werte entfernen. Lassen Sie uns den Mechanismus zum Entfernen von Werten aus dem Cache anhand verschiedener Kriterien diskutieren.

3.1. Räumung nach Größe

Wir können die Größe unseres Caches mit maximumSize () begrenzen . Wenn der Cache das Limit erreicht, werden die ältesten Elemente entfernt.

Im folgenden Code beschränken wir die Cache-Größe auf 3 Datensätze:

@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. Räumung nach Gewicht

Wir können die Cache-Größe auch mithilfe einer benutzerdefinierten Gewichtungsfunktion begrenzen . Im folgenden Code verwenden wir die Länge als benutzerdefinierte Gewichtsfunktion:

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

Hinweis: Der Cache entfernt möglicherweise mehr als einen Datensatz, um Platz für einen neuen großen Datensatz zu lassen.

3.3. Räumung durch die Zeit

Neben der Verwendung der Größe zum Entfernen alter Datensätze können wir auch die Zeit verwenden. Im folgenden Beispiel passen wir unseren Cache an, um Datensätze zu entfernen, die 2 ms lang inaktiv waren :

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

Wir können Datensätze auch basierend auf ihrer gesamten Live-Zeit entfernen . Im folgenden Beispiel entfernt der Cache die Datensätze nach 2 ms Speicherzeit:

@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. Schwache Schlüssel

Als nächstes wollen wir sehen, wie unsere Cache-Schlüssel schwache Referenzen haben - damit der Garbage Collector Cache-Schlüssel sammeln kann, auf die an keiner anderen Stelle verwiesen wird.

Standardmäßig haben sowohl Cache-Schlüssel als auch Werte starke Referenzen, aber wir können dafür sorgen, dass unser Cache die Schlüssel mithilfe schwacher Referenzen mithilfe von schwachKeys () speichert, wie im folgenden Beispiel:

@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. Weiche Werte

Wir können dem Garbage Collector erlauben, unsere zwischengespeicherten Werte mithilfe von softValues ​​() wie im folgenden Beispiel zu erfassen :

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

Hinweis: Viele weiche Referenzen können sich auf die Systemleistung auswirken. Es wird bevorzugt, maximumSize () zu verwenden .

6. Griff null Values

Lassen Sie uns nun sehen, wie mit Cache- Nullwerten umgegangen wird. Standardmäßig löst der Guava-Cache Ausnahmen aus, wenn Sie versuchen, einen Nullwert zu laden, da es keinen Sinn macht, eine Null zwischenzuspeichern .

Wenn der Nullwert jedoch etwas in Ihrem Code bedeutet, können Sie die optionale Klasse wie im folgenden Beispiel gut verwenden:

@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. Aktualisieren Sie den Cache

Als nächstes wollen wir sehen, wie wir unsere Cache-Werte aktualisieren.

7.1. Manuelle Aktualisierung

Wir können einen einzelnen Schlüssel mithilfe von LoadingCache.refresh (Schlüssel) manuell aktualisieren.

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

Dadurch wird der CacheLoader gezwungen , den neuen Wert für den Schlüssel zu laden .

Bis der neue Wert erfolgreich geladen wurde, wird der vorherige Wert des Schlüssels vom get (Schlüssel) zurückgegeben .

7.2. Automatische Aktualisierung

Wir können CacheBuilder.refreshAfterWrite (Dauer) verwenden , um zwischengespeicherte Werte automatisch zu aktualisieren .

@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

In diesem Lernprogramm wurden viele Anwendungsfälle des Guava-Cache behandelt - von der einfachen Verwendung über das Entfernen von Elementen, das Aktualisieren und Vorladen des Caches bis hin zu Benachrichtigungen zum Entfernen.

Alle Beispiele finden Sie wie gewohnt auf GitHub.