グアバキャッシュ

1概要

このチュートリアルでは、 Guava Cache の実装 - 基本的な使い方、削除ポリシー、キャッシュの更新、そして興味深い一括操作 - を見ていきます。

最後に、キャッシュが送信できる削除通知の使い方を見ていきます。

2 Guava Cache の使い方

簡単な例から始めましょう - String インスタンスの大文字形式をキャッシュしましょう。

まず、キャッシュに格納されている値を計算するための CacheLoader を作成します。これから、私たちは与えられた仕様を使ってキャッシュを構築するために便利な 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());
}

キャッシュに「hello」キーの値がないことに注目してください。したがって、値が計算されキャッシュされます。

また、 getUnchecked() 操作を使用していることにも注意してください - まだ存在していない場合は、これが計算されてキャッシュにロードされます。

3立ち退きポリシー

どのキャッシュもある時点で値を削除する必要があります。さまざまな基準を使用して、キャッシュから値を削除するメカニズムについて説明しましょう。

3.1. サイズによる立ち退き

maximumSize() を使用して キャッシュのサイズを制限 することができます。キャッシュが制限に達すると、最も古いアイテムが削除されます。

次のコードでは、キャッシュサイズを3レコードに制限します。

@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. 重量による立ち退き

カスタム重み関数を使って キャッシュサイズを制限 することもできます。次のコードでは、カスタムの重み関数として length を使用しています。

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

注:キャッシュは、新しい大きなレコード用のスペースを空けるために複数のレコードを削除することがあります。

3.3. 時間による立ち退き

サイズを使用して古いレコードを削除するだけでなく、時間を使用することもできます。次の例では、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"));
}

私たちはまた、 彼らの合計ライブタイムに基づいて レコードを削除することができます。次の例では、キャッシュは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弱い鍵

次に、キャッシュキーに弱い参照を持たせる方法を見てみましょう。ガベージコレクタは、他の場所で参照されていないキャッシュキーを収集できます。

デフォルトでは、キャッシュキーと値の両方に強い参照がありますが、次の例のように 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ソフトバリュー

次の例のように 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);
}

注:多くのソフト参照はシステムパフォーマンスに影響を与える可能性があります - maximumSize() を使用するのが好ましいです

6. __ NULL値の処理

それでは、キャッシュのNULL値を処理する方法を見てみましょう。 null 値をロードしようとすると、デフォルトでは Guava Cache は例外をスローします - null をキャッシュしても意味がありません。

ただし、 null valueがコード内で何かを意味する場合は、次の例のように Optional クラスをうまく利用できます。

@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. キャッシュを更新する

次に、キャッシュ値を更新する方法を見てみましょう。 refreshAfterWrite() を使用してキャッシュを自動的に更新できます。

次の例では、キャッシュは1分ごとに自動的に更新されます。

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

注: refresh(key) を使用して特定のレコードを手動で更新することができます。

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

putAll() メソッドを使用して、キャッシュに複数のレコードを挿入できます。次の例では、 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削除通知

場合によっては、レコードがキャッシュから削除されたときにいくつかのアクションを実行する必要があります。それでは、 RemovalNotification について説明しましょう。

削除されたレコードの通知を受け取るために RemovalListener を登録することができます。 getCause() メソッドを使用して、削除の原因にアクセスすることもできます。

次の例では、キャッシュ内の4番目の要素がサイズのために RemovalNotification されたときに受信します。

@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キャッシュの実装に関するいくつかの簡単な補足説明を次に示します。

  • スレッドセーフです

  • put(key、value) を使って手動でキャッシュに値を挿入できます

  • CacheStats ()を使ってキャッシュパフォーマンスを測定できます。

hitRate() missRate() 、..)

11結論

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