Guava Cache – 瓜娃缓存

最后修改: 2014年 10月 9日

中文/混合/英文(键盘快捷键:t)

1. Overview

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.

在本教程中,我们将重点介绍Guava Cache的实现,包括基本用法、驱逐策略、刷新缓存和一些有趣的批量操作。

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

最后,我们将讨论如何使用缓存所能发出的移除通知。

2. How to Use Guava Cache

2.如何使用Guava Cache[/strong]

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

让我们从一个简单的例子开始,即缓存String实例的大写形式。

First, we’ll create the CacheLoader, which is used to compute the value stored in the cache. From this, we’ll use the handy CacheBuilder to build our cache using the given specifications:

首先,我们将创建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());
}

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

注意到我们的 “hello “键在缓存中没有值,所以这个值是经过计算和缓存的。

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

还要注意的是,我们使用的是getUnchecked()操作,它计算并将值加载到缓存中,如果它还不存在的话。

3. Eviction Policies

3.驱逐政策

Every cache needs to remove values at some point. Let’s discuss the mechanism of evicting values out of the cache using different criteria.

每个缓存都需要在某个时间点移除数值。让我们来讨论一下用不同的标准将值从缓存中驱逐出去的机制。

3.1. Eviction by Size

3.1.按规模驱逐

We can limit the size of our cache using maximumSize(). If the cache reaches the limit, it evicts the oldest items.

我们可以使用maximumSize()来限制缓存的大小。如果缓存达到极限,它将驱逐最旧的项目。

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. Eviction by Weight

3.2.按体重驱逐

We can also limit the cache size using a custom weight function. In the following code, we’ll use the length as our custom weight function:

我们也可以用一个自定义的权重函数来限制缓存的大小。在下面的代码中,我们将使用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"));
}

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

注意:缓存可能会删除一个以上的记录,以便为新的大记录留出空间。

3.3. Eviction by Time

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:

除了使用大小来驱逐旧记录外,我们还可以使用时间。在下面的例子中,我们将自定义我们的缓存,以删除闲置了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"));
}

We can also evict records based on their total live time. In the following example, the cache will remove the records after they’ve been stored for 2ms:

我们还可以根据记录的总活期时间对其进行驱逐。在下面的例子中,缓存将在记录存储了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. Weak Keys

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():

默认情况下,缓存的键和值都是强引用,但我们可以通过使用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. Soft Values

5.软价值

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

我们还可以通过使用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().

注意:太多的软引用可能会影响系统性能,所以首选是使用maximumSize()

6. Handle null Values

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.

现在我们来看看如何处理缓存的null值。默认情况下,Guava Cache会在我们试图加载一个null值时抛出异常,因为缓存一个null没有任何意义。

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

但是如果一个null值在我们的代码中意味着什么,那么我们可以很好地利用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. Refresh the Cache

7.刷新缓存

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

接下来,我们将学习如何刷新我们的缓存值。

7.1. Manual Refresh

7.1.手动刷新

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

我们可以在LoadingCache.refresh(key)的帮助下,手动刷新一个单键:

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

This will force the CacheLoader to load the new value for the key.

这将迫使CacheLoaderkey.加载新值。

Until the new value is successfully loaded, the previous value of the key will be returned by the get(key).

在新的值被成功加载之前,key的前一个值将被get(key)返回。

7.2. Automatic Refresh

7.2.自动刷新

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

我们还可以使用CacheBuilder.refreshAfterWrite(duration)来自动刷新缓存的值。

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

It’s important to understand that refreshAfterWrite(duration) only makes a key eligible for the refresh after the specified duration. The value will actually be refreshed only when a corresponding entry is queried by get(key).

重要的是要理解,refreshAfterWrite(duration)仅使键有资格在指定的持续时间后被刷新。实际上,只有当相应的条目被get(key)查询时,该值才会被刷新

8. Preload the Cache

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:

我们可以使用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

9.移除通知

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

有时我们需要在一条记录从缓存中删除时采取行动,所以我们将讨论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.

我们可以注册一个RemovalListener来获取记录被删除的通知。我们还可以通过getCause()方法来获取删除的原因。

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

在下面的例子中,当缓存中的第四个元素因其大小而被移除时,会收到一个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. Notes

10.注释

Finally, here are a few additional quick notes about the Guava cache implementation:

最后,这里有一些关于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. Conclusion

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.

在这篇文章中,我们探讨了很多Guava Cache的使用案例。我们讨论的主题包括简单的使用,驱逐元素,刷新和预加载缓存,以及移除通知。

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

像往常一样,所有的例子都可以在GitHub上找到超过