1. 概要

In this tutorial, we’ll focus on the Guava Cache implementation, including basic usage, eviction policies, refreshing the cache, and some interesting bulk operations.

Finally, we’ll discuss how to use the removal notifications the cache is able to send out.

2. Guavaキャッシュの使用方法

Let’s start with a simple example of caching the uppercase form of String instances.

First, we’ll create the CacheLoader, which is used to compute the value stored in the cache. これから、便利な CacheBuilder を使用して、指定された仕様を使用してキャッシュを構築します。

@Test
public void whenCacheMiss_thenValueIsComputed() {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };

    LoadingCache<String, String> cache;
    cache = CacheBuilder.newBuilder().build(loader);

    assertEquals(0, cache.size());
    assertEquals("HELLO", cache.getUnchecked("hello"));
    assertEquals(1, cache.size());
}

Notice how there’s no value in the cache for our “hello” key, so the value is computed and cached.

Also note that we’re using the getUnchecked() operation, which computes and loads the value into the cache if it doesn’t already exist.

3. 立ち退きポリシー

すべてのキャッシュは、ある時点で値を削除する必要があります。 Let’s discuss the mechanism of evicting values out of the cache using different criteria.

3.1. サイズ別のエビクション

maximumSize()を使用して、キャッシュのサイズを制限できます。 If the cache reaches the limit, it evicts the oldest items.

In the following code, we’ll limit the cache size to three records:

@Test
public void whenCacheReachMaxSize_thenEviction() {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };
    LoadingCache<String, String> 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. 重量による退き

カスタムの重み関数を使用して、キャッシュサイズを制限することもできます。 In the following code, we’ll use the length as our custom weight function:

@Test
public void whenCacheReachMaxWeight_thenEviction() {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };

    Weigher<String, String> weighByLength;
    weighByLength = new Weigher<String, String>() {
        @Override
        public int weigh(String key, String value) {
            return value.length();
        }
    };

    LoadingCache<String, String> 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"));
}

Note: the cache may remove more than one record to leave room for a new large one.

3.3. 時間による退き

In addition to using size to evict old records, we can use time. In the following example, we’ll customize our cache to remove records that have been idle for 2ms:

@Test
public void whenEntryIdle_thenEviction()
  throws InterruptedException {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };

    LoadingCache<String, String> 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"));
}

合計ライブ時間に基づいてレコードを削除することもできます。 In the following example, the cache will remove the records after they’ve been stored for 2ms:

@Test
public void whenEntryLiveTimeExpire_thenEviction()
  throws InterruptedException {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };

    LoadingCache<String, String> 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. 弱鍵

Next, we’ll demonstrate how to make our cache keys have weak references, allowing the garbage collector to collect cache keys that aren’t referenced elsewhere.

By default, both cache keys and values have strong references, but we can make our cache store the keys using weak references by using weakKeys():

@Test
public void whenWeakKeyHasNoRef_thenRemoveFromCache() {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };

    LoadingCache<String, String> cache;
    cache = CacheBuilder.newBuilder().weakKeys().build(loader);
}

5. ソフトバリュー

We can also allow the garbage collector to collect our cached values by using softValues():

@Test
public void whenSoftValue_thenRemoveFromCache() {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };

    LoadingCache<String, String> cache;
    cache = CacheBuilder.newBuilder().softValues().build(loader);
}

Note: too many soft references may affect the system performance, so the preferred option is to use maximumSize().

6. null値を処理する

Now let’s see how to handle cache null values. By default, Guava Cache will throw exceptions if we try to load a null value, as it doesn’t make any sense to cache a null.

But if a null value means something in our code, then we can make good use of the Optional class:

@Test
public void whenNullValue_thenOptional() {
    CacheLoader<String, Optional<String>> loader;
    loader = new CacheLoader<String, Optional<String>>() {
        @Override
        public Optional<String> load(String key) {
            return Optional.fromNullable(getSuffix(key));
        }
    };

    LoadingCache<String, Optional<String>> 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. キャッシュを更新する

Next, we’ll learn how to refresh our cache values.

7.1. 手動更新

We can refresh a single key manually with the help of LoadingCache.refresh(key):

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

これにより、CacheLoaderキーの新しい値をロードします。

新しい値が正常にロードされるまで、キーの以前の値が get(key)。によって返されます。

7.2. 自動更新

We can also use CacheBuilder.refreshAfterWrite(duration) to automatically refresh cached values:

@Test
public void whenLiveTimeEnd_thenRefresh() {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };

    LoadingCache<String, String> cache;
    cache = CacheBuilder.newBuilder()
      .refreshAfterWrite(1,TimeUnit.MINUTES)
      .build(loader);
}

refreshAfterWrite(duration)は、指定された期間の後にのみキーを更新の対象にすることを理解することが重要です。 値は、対応するエントリが get(key)。によって照会された場合にのみ実際に更新されます。

8. キャッシュをプリロードする

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

@Test
public void whenPreloadCache_thenUsePutAll() {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            return key.toUpperCase();
        }
    };

    LoadingCache<String, String> cache;
    cache = CacheBuilder.newBuilder().build(loader);

    Map<String, String> map = new HashMap<String, String>();
    map.put("first", "FIRST");
    map.put("second", "SECOND");
    cache.putAll(map);

    assertEquals(2, cache.size());
}

9. 削除通知

Sometimes we need to take action when a record is removed from the cache, so we’ll discuss RemovalNotification.

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

In the following example, a RemovalNotification is received when the fourth element in the cache is removed because of its size:

@Test
public void whenEntryRemovedFromCache_thenNotify() {
    CacheLoader<String, String> loader;
    loader = new CacheLoader<String, String>() {
        @Override
        public String load(final String key) {
            return key.toUpperCase();
        }
    };

    RemovalListener<String, String> listener;
    listener = new RemovalListener<String, String>() {
        @Override
        public void onRemoval(RemovalNotification<String, String> n){
            if (n.wasEvicted()) {
                String cause = n.getCause().name();
                assertEquals(RemovalCause.SIZE.toString(),cause);
            }
        }
    };

    LoadingCache<String, String> 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. ノート

最後に、Guavaキャッシュの実装に関するいくつかの追加の簡単なメモを次に示します。

  • it’s thread-safe
  • we can insert values into the cache manually using put(key,value)
  • we can measure our cache performance using CacheStats ( hitRate(), missRate(), ..)

11. 結論

In this article, we explored a lot of use cases for Guava Cache. The topics we discussed include simple use, evicting elements, refreshing and preloading the cache, as well as removal notifications.

As usual, all of the examples can be found over on GitHub.