Guava Cache使用介绍

    cache在各种系统中都有使用到,这里介绍java神器guava中的一个本地cache实现。关于guava中的cache,官方wiki上的介绍已经相当的详细,可以参考。以下是从网上搜索到的中文版本。所以打算直接转过来,供大家参考。正常情况下的使用方法如下所示:

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

适用范围

    Cache在真实场景中有着相当广的使用范围。例如,当一个值要通过昂贵的计算和检索来获取,并且这个结果会被多次使用到,这个时候你就应该考虑使用缓存了。 Cache有点类似于ConcurrentMap,但并不是完全一样。最根本的区别在于ConcurrentMap会持有添加到Map中的所有元素直到元素被移除为止。Cache通过相关的配置项可以自动驱逐相关的缓存项,达到约束缓存占用的目的。有些情景中LoadingCache通过自动的内存载入甚至可以不进行驱逐缓存项。 通常,Guava缓存工具适用于如下的情景: 1、你愿意牺牲一些内存来提升速度。 2、你期待缓存项的查询大于一次。 3、缓存不需要存储在除了内存以外的更多数据。 如果你满足以上的情景,那么Guava Cache工具集会是你的正确选择。 获取一个Cache实例,可以通过CacheBuilder的工厂模式来获得,如上面所示的示例代码,但是定制你的Cache才是真正有趣的部分。 注意:如果你不需要Cache的特性,ConcurrentHashMap会更高效的使用内存,但是ConcurrentHashMap就很难实现缓存特性。

概述

    首先要问你自己关于你的Cache,是否有通过一个key调用函数得到一个value或者通过一个key进行相关计算得到的value。如果是这样,你可以使用CacheLoader。如果不是,你需要重写默认的逻辑,当你还想能够自动回去一个不存在的key的默认值,你可以通过一个Callable去获取一个call。元素插入可以通过调用Cache.put直接插入到缓存中,但自动的cache loading是首选,因为这样更容易达到cache以为的kye值获取的一致性。

CacheLoader

    LoadingCache是通过CacheLoader构建的一个Cache。构建一个CacheLoader的经典过程是实现一个V load(K key)。所以,你可以通过下面的示例代码来构建一个LoadingCache:

LoadingCache graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .build(
           new CacheLoader() {
             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)。这个方法将返回已经缓存在Cache中的Value,或者使用CacheLoader自动的载入一个新Value到Cache中。因为CacheLoader可能会抛出Exception,LoadingCache.get(K) throws ExecutionExcaption。如果你有一个默认的CacheLoader没有什么任何的受检异常,你可以通过使用getUnchecked(K),然而你不能使用getUnchecked在一个声明了受检异常的CacheLoader。

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

直接插入

    Value可以通过使用cache.put(key,value)直接插入,这个方法调用会覆盖原先插入的相同Key对应的Value。也可以通过调用 Cache.asMap()将Cache转化成一个ConcurrentMap,然后通过ConcurrentMap的方法来操作内存,不过这样做之后自动Cache loaded的功能就失效了。更进一步说,使用Cache.asMap().putIfAbsent这样的方式插入内存,就可以完美的满足Cache.get(K, Callable)这样的获取并且绕过自动cache loading,也可以使用CacheLoader和Callable。

清理内存

    一个很残酷的现实是我们肯定没有足够多的内存来缓存我们所需要的所有东西。你必须决定:什么时候持有缓存是不值得的?Guava提供了3中基本的形式:基于容量的清理,基于时间的清理,基于引用的清理。

基于容量的清理

    如果你想控制缓存容量不要超过特定的值,只需要使用CacheBuilder.maximumSize(long)。缓存将使用最近最久未使用的规则清理相关的元素。警告:缓存不会再容量严格达到这个上限的时候进行清理,而有可能在容量接近这个阀值的时候就进行清理。

    另外,如果不同的缓存元素具有不同的权重,例如,如果缓存的Value占用内存存在悬殊,你可以通过使用

CacheBuilder.weigher(Weigher)来设置权重,缓存的最大权重使用CacheBuilder.maximumWeight(long)进行设置。

LoadingCache graphs = CacheBuilder.newBuilder()
       .maximumWeight(100000)
       .weigher(new Weigher() {
          public int weigh(Key k, Graph g) {
            return g.vertices().size();
          }
        })
       .build(
           new CacheLoader() {
             public Graph load(Key key) { // no checked exception
               return createExpensiveGraph(key);
             }
           });

基于时间的清理

    CacheBuilder提供了两种方式进行时间清理设置:

    1、expireAfterAccess(long, TimeUnit) 。当你希望元素在最后一次读或者写访问之后,超过一定时间值之后被清理,你可以使用这种方式的设置。注意,这种方式最后的效果会和基于容量的清理类似。

    2、expireAfterWrite(long, TimeUnit) 。如果你希望元素在创建或者被替换之后,超过一定时间值之后被清楚,你可以使用这种方式的设置。这种方式适合于对写入有一定时效性的元素。

测试时间清理

    一般的缓存测试时间清理是非常痛苦的。你不得让计算机等待2秒,来测试一个2秒钟缓存失效的用例。Guava中,在你的CacheBuilder中可以使用Ticker接口和CacheBuilder.ticker(Ticker)方法来制定具体的时间,而不用真的去等待系统时间。

基于引用的清理

    Guava允许你遵循垃圾回收期的机制对缓存元素进行清理,通过弱引用回收key和value,通过软引用来回收vaule。

    1、CacheBuilder.weakKeys()。使用弱引用的方式存储key。这允许元素在没有任何强应用和软引用指向对应的key的时候进行垃圾回收。因为垃圾回收依赖于恒定(==),所以这将导致使用恒等来进行key值比较,而不再使用equals()。

    2、CacheBuilder.weakValues()。使用弱引用的方式存储value。这允许元素在没有任何强应用和软引用指向对应的key的时候进行垃圾回收。因为垃圾回收依赖于恒定(==),所以这将导致使用恒等来进行value值比较,而不再使用equals()。

    3、CacheBuilder.softValues()。将value包装成软引用。垃圾回收使用全局的最近最久未使用方式来管理软引用对象,进行内存需求响应。考虑到使用软引用对性能造成的影响,我们一般建议使用预见性更强的 maximum cache size 方式累替换。使用softValues()将导致value的比较使用恒等(==)的方式来替换equals()。