本章主要介绍Guava提供的有关缓存方面的工具。本来第三章应该对应官方文档的话,是介绍Guava实现的一整套有向图无向图的工具类,但是考虑到图模型在我们日常开发中较少用到(web开发中),所以暂缓到后面有时间再补充。

例子

Guava的官方文档一上来就怼给你一个例子,我也学学:

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

适用范围

缓存这个不用说了,很多情况下我们的确需要缓存。 Guava文档说了,它提供的缓存比较像ConcurrentMap,但是也是有区别的,我们从上面的例子就能看出来区别,Guava提供的可以设定缓存的过期时间,时间一到entry就被自动移除了,还允许你注册监听器Listener,移除的时候告诉你一声,做点儿额外的逻辑。 总之,总的来说,下面这些情况同时满足时,Guava提供的缓存比较适合你: - 你想要花一些内存来提高程序的速度 - 你预期你的key值会不止一次地被查询 - 你想要缓存的东西不会撑爆虚拟机的最大内存 - Guava的缓存是单机缓存,如果你有多台机器干同样的事儿,那么你可能用不上这个缓存,还是考虑Memcached或者redis

如果上面的这些都满足了,那么恭喜你,可以使用Guava的缓存了。

Population

这特么怎么翻译,常用方法?懵逼中…… Guava的缓存实现比较有意思,我们看上面的例子可以注意到最终build时传入了一个CacheLoader,这就是Guava强烈推荐的第一种缓存。 ### CacheLoader CacheLoader主要是用在那种可以根据key直接计算得到Value的情况,就是Key和Value之间有个函数关系,这样的话,你就可以使用这个CacheLoader来得到缓存:

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

下面的get方法会得到value,如果此时缓存内部还没有的话,就调用你传入的load方法计算得到,同时缓存起来。 注意到get方法被try-catch块包起来了,这是因为你传入的Load方法有抛出受检异常。 如果没有抛出受检异常,就不用这么麻烦,直接使用getUnchecked(K)方法,就不需要try-catch块包起来了。注意,抛出受检异常的情况,你绝对不能使用getUnchecked(K)方法。 当然,还可以使用getAll(Iterable<? extends K>)方法来直接获得多个Value,不过这意味着每个Key都有可能要经过Load方法计算得到value,所以为了性能考虑,你也可以实现CacheLoader.loadAll方法,来覆盖默认的行为。 ### Callable 先看例子:

Cache<Key, Value> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .build(); // look Ma, no CacheLoader
...
try {
  // If the key wasn't in the "easy to compute" group, we need to
  // do things the hard way.
  cache.get(key, new Callable<Value>() {
    @Override
    public Value call() throws AnyException {
      return doThingsTheHardWay(key);
    }
  });
} catch (ExecutionException e) {
  throw new OtherException(e.getCause());
}

可以看到,在build的时候并没有传入一个Loader进去,而是在get的地方传入了一个Callbale<Value>进去,很显然,这种情况适合在创建缓存的时候,Key和Value的关系还不能确定的情况下。 ### Inserted Directly(直接插入) 我们经常讨论的缓存都是这种思路,上面两种思路我只能说谷歌的人果然脑洞惊奇。 使用put(key,value)来直接将缓存的东西放进去,如果已经有了,就覆盖。 还可以通过Cache.asMap()得到一个ConcurrentMap的View,但是Guava好像不推荐使用Cache.asMap().putIfAbsent,虽然这样也能改变缓存,但是似乎有些问题,可惜原文解释的太烂,我没有看懂,贴出原文如下: > Changes can also be made to a cache using any of the ConcurrentMap methods exposed by the Cache.asMap() view. Note that no method on the asMap view will ever cause entries to be automatically loaded into the cache. Further, the atomic operations on that view operate outside the scope of automatic cache loading, so Cache.get(K, Callable) should always be preferred over Cache.asMap().putIfAbsent in caches which load values using either CacheLoader or Callable.

但愿看懂的好心人能在下面留言告诉我怎么回事。

缓存释放

Guava提供了三种缓存自动释放的方法:基于大小、基于时间、基于引用 下面逐个介绍 ### 基于大小的缓存释放 上面的例子中实际上演示了这种方法,只要在创建的时候,通过CacheBuilder.maximumSize(long)指定大小的最大值即可。 注意: - Guava不会在到了最大大小的时候才开始释放缓存,而是在缓存已用大小逼近最大大小的时候就开始释放了 - 释放缓存的时候把谁从缓存里删掉呢?原文说,会删最近不常使用的,或者最常使用的。原文:The cache will try to evict entries that haven't been used recently or very often.

还有另外一种方式:

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

这主要是因为,放在缓存中的对象可能大小差别很大,如果每个都当做1来看待,可能会对什么时候释放缓存评估失误。 这个时候需要开发者使用weigher来为每个对象计算一个大小,并指定最大值。 注意: - 这种方法也是一样,在缓存使用量逼近你设置的max值时,缓存释放的工作就已经开始了,不会等到缓存用完才开始 - weigher是在缓存创建的时候就计算好的,不会再改变,是静态的

基于时间的缓存释放