Guava 中的缓存

Caches

示例

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .removalListener(MY_LISTENER)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

适用性

缓存在多种场景下都是十分有用的。例如,当值的计算或检索代价很高时,并且您需要多次在某个输入上使用它的值,您应该考虑使用缓存。

Cache 类似于 ConcurrentMap,但并不完全相同。最根本的区别在于 ConcurrentMap 会持久保存添加到其中的所有元素,直到它们被明确移除。另一方面,缓存通常被配置为自动移除元素,以便限制其内存占用。在某些情况下,即使它没有移除元素,由于自​​动缓存加载的特性, LoadingCache 也变得很有用。

通常,Guava 缓存工具程序适用于以下情况:

  • 你愿意花些内存来提高速度。
  • 你希望有时会多次查询键值。
  • 你需要缓存数据的要小于 RAM 的容量。 (Guava 缓存是一个单独运行的应用程序的本地缓存。它们不会将数据存储在文件中或外部服务器上。如果这不符合你的需求,请考虑像 Memcached 这样的工具。)

如果以上几点都适用于您的应用,那么 Guava 缓存工具程序将很适合你!

如上面的示例代码所示,你可以使用 CacheBuilder 构建器模式获取 Cache 对象,但自定义 Cache 也是很有趣的部分。

注意:如果您不需要 Cache 的特性,ConcurrentHashMap 的内存效率会更高 - 但是使用任何旧的 ConcurrentMap 复制大多数 Cache 的功能是极其困难或不可能的。

Population

在设计自己的缓存时要考虑的第一个问题是:是否有一些合理的默认函数来加载或计算与 key 相关的值? 如果是这样,您应该使用 CacheLoader。 如果没有,或者你需要覆盖默认值,但仍然需要原子 “get-if-absent-compute” 语义,则应该将 Callable 传递给 get 调用。 可以使用 Cache.put 直接插入元素,但首选自动缓存加载,因为它可以更容易地推断所有缓存内容的一致性。

From a CacheLoader

LoadingCache 是使用附加的 CacheLoader 构建的 Cache。 创建 CacheLoader 通常与实现 V load(K key) throws Exception 方法一样简单。 因此,例如,您可以使用以下代码创建 LoadingCache

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

...
try {
  return graphs.get(key);
} catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}

查询 LoadingCache 的标准方法是使用方法 get(K)。 这将返回已缓存的值,或者使用 CacheCacheLoader 以原子方式将新值加载到缓存中。 因为 CacheLoader 可能抛出异常,所以 LoadingCache.get(K) 方法签名要抛出 ExecutionException。(如果缓存加载器抛出未检异常, get(K) 将抛出一个包含它的 UncheckedExecutionException。)你还可以选择使用 getUnchecked(K),它包装 UncheckedExecutionException 中的所有异常,但如果底层CacheLoader 正常抛出受检异常,这可能会导致奇怪的行为。

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .expireAfterAccess(10, TimeUnit.MINUTES)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) { // no checked exception
               return createExpensiveGraph(key);
             }
           });

...
return graphs.getUnchecked(key);

可以使用方法 getAll(Iterable <?extends K>) 执行批量查找。 默认情况下,getAll 将为缓存中不存在的每个密钥发出对 CacheLoader.load 的单独调用。 当批量检索比许多单独查找更有效时,您可以覆盖 CacheLoader.loadAll 来扩展它。 getAll(Iterable) 的性能将相应提高。

请注意,您可以编写一个 CacheLoader.loadAll 实现来加载未特别请求的键的值。 例如,如果计算某个组中任何键的值会为您提供组中所有键的值,则 loadAll 可能会同时加载该组的其余部分。

推荐阅读更多精彩内容