グアバキャッシュ

グアバキャッシュ

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)を使用してキャッシュに手動で値を挿入できます

  • CacheStatshitRate()missRate()、..)を使用してキャッシュパフォーマンスを測定できます。

11. 結論

このチュートリアルでは、単純な使用法から要素の削除、キャッシュの更新とプリロード、削除通知まで、Guava Cacheの多くのユースケースを使用しました。