Кеш Гуавы

Guava Cache

1. обзор

В этом руководстве мы рассмотрим реализациюGuava Cache - базовое использование, политики выселения, обновление кеша и некоторые интересные массовые операции.

Наконец, мы рассмотрим использование уведомлений об удалении, которые может отправлять кеш.

2. Как использовать кэш Guava

Начнем с простого примера - давайте кешируем заглавные буквы экземпляров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. Выселение по размеру

Мы можемlimit the size of our cache, используяmaximumSize(). Если кэш достигнет предела, самые старые элементы будут выселены.

В следующем коде мы ограничиваем размер кэша 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 кеша. По умолчаниюGuava Cache будет вызывать исключения, если вы попытаетесь загрузить значениеnull, поскольку нет смысла кэшировать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().

В следующем примере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 - от простого использования до удаления элементов, обновления и предварительной загрузки кэша и уведомлений об удалении.

Related