グアバキャッシュ
1. 概要
このチュートリアルでは、Guava Cacheの実装(基本的な使用法、エビクションポリシー、キャッシュの更新、およびいくつかの興味深い一括操作)について説明します。
最後に、キャッシュが送信できる削除通知を使用する方法を見ていきます。
2. グアバキャッシュの使い方
簡単な例から始めましょう–大文字のStringインスタンスをキャッシュしましょう。
まず、キャッシュに保存されている値の計算に使用されるCacheLoaderを作成します。 これから、便利なCacheBuilderを使用して、指定された仕様を使用してキャッシュを構築します。
@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());
}
「hello」キーのキャッシュに値がないことに注意してください。したがって、値が計算されてキャッシュされます。
また、getUnchecked()操作を使用していることにも注意してください。これは、値がまだ存在しない場合、値を計算してキャッシュにロードします。
3. 立ち退きポリシー
すべてのキャッシュは、ある時点で値を削除する必要があります。 さまざまな基準を使用して、キャッシュから値を削除するメカニズムについて説明しましょう。
3.1. サイズによる立ち退き
maximumSize()を使用してlimit the size of our cacheを実行できます。 キャッシュが制限に達すると、最も古いアイテムが削除されます。
次のコードでは、キャッシュサイズを3レコードに制限しています。
@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. 重量による立ち退き
カスタムの重み関数を使用してlimit the cache sizeすることもできます。 次のコードでは、カスタムの重み関数としてlengthを使用しています。
@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"));
}
注:キャッシュは、複数のレコードを削除して、新しい大きなレコード用のスペースを空けることがあります。
3.3. 時間による立ち退き
サイズを使用して古いレコードを削除するほかに、時間を使用できます。 次の例では、キャッシュをremove records that have been idle for 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"));
}
evict records based on their total live timeも可能です。 次の例では、キャッシュは2ミリ秒の保存後にレコードを削除します。
@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. 弱鍵
次に、キャッシュキーの参照を弱くする方法を見てみましょう。ガベージコレクターが他の場所で参照されていないキャッシュキーを収集できるようにします。
デフォルトでは、キャッシュキーと値の両方に強参照がありますが、次の例のように、weakKeys()を使用して弱参照を使用してキャッシュにキーを格納させることができます。
@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. ソフトバリュー
次の例のように、softValues()を使用して、ガベージコレクターがキャッシュされた値を収集できるようにすることができます。
@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);
}
注:多くのソフト参照はシステムパフォーマンスに影響を与える可能性があります。maximumSize()を使用することをお勧めします。
6. null値を処理する
それでは、キャッシュnullの値を処理する方法を見てみましょう。 デフォルトでは、nullの値を読み込もうとすると、Guava Cacheは例外をスローします。これは、nullをキャッシュしても意味がないためです。
ただし、nullの値がコード内の何かを意味する場合は、次の例のようにOptionalクラスをうまく利用できます。
@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. キャッシュを更新する
次に、キャッシュ値を更新する方法を見てみましょう。 refreshAfterWrite()を使用してキャッシュを自動的に更新できます。
次の例では、キャッシュは1分ごとに自動的に更新されます。
@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);
}
注:refresh(key)を使用して、特定のレコードを手動で更新できます。
8. キャッシュをプリロードする
putAll()メソッドを使用して、キャッシュに複数のレコードを挿入できます。 次の例では、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
レコードがキャッシュから削除されたときに、いくつかのアクションを実行する必要がある場合があります。それでは、RemovalNotificationについて説明しましょう。
RemovalListenerを登録して、レコードが削除されたという通知を受け取ることができます。 また、getCause()メソッドを介して削除の原因にアクセスできます。
次のサンプルでは、キャッシュ内の4番目の要素がサイズのためにRemovalNotificationを受信しています。
@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. ノート
最後に、Guavaキャッシュの実装に関する追加の簡単なメモを次に示します。
-
スレッドセーフです
-
put(key,value)を使用してキャッシュに手動で値を挿入できます
-
CacheStats(hitRate()、missRate()、..)を使用してキャッシュパフォーマンスを測定できます。
11. 結論
このチュートリアルでは、単純な使用法から要素の削除、キャッシュの更新とプリロード、削除通知まで、Guava Cacheの多くのユースケースを使用しました。