Guava Cache的使用

缓存在Guav中的应用

Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

通常来说,Guava Cache适用于:
你愿意消耗一些内存空间来提升速度。
你预料到某些键会被查询一次以上。
缓存中存放的数据总量不会超出内存 容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)

我们使用的Cache一般都会有自动加载的功能

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(key)方法,这个方法要不就是返回已经缓存的值,要使用CacheLoader向缓存原子性的增加新值,由于CacheLoader可能会抛出异常,所以我们往往需要在get()try catch来捕获异常。
getAll(Iterable)方法用来执行批量查询。默认情况下,对每个不在缓存中的键,getAll方法会单独调用CacheLoader.load来加载缓存项。如果批量的加载比多个单独加载更高效,你可以重载CacheLoader.loadAll来利用这一点。getAll(Iterable)的性能也会相应提升。
而且Cache支持,在get()方法中加入CallAble这个方法会返回缓存中的值,或者用给定的Callable来计算并把结果放入到缓存中

显示的插入值

使用cache.put(key, value)方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中。
进一步说,asMap视图的原子运算在Guava Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K,V),
Cache.get(K, Callable) 应该总是优先使用, 因为Gava保证了get()是原子性的更新缓存信息

缓存回收

基于容量的回收

如果需要缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)
这种是简单的回收最近没有使用或者总体上使用很少的缓存项

另外使用权重值回收也是不错的选择

LoadingCache cache1 = CacheBuilder.newBuilder().maximumWeight(3).weigher((String key, String value) -> {
            return key.length() + value.length();
        }).build(new CacheLoader() {


            @Override
            public String load(String key) throws Exception {
                return new Random(System.currentTimeMillis()).nextInt(100) + key;
            }
        });

定式回收

CacheBuilder提供了两种定时回收的方法,

  • expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样

  • expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

基于引用的回收

通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:

CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用键的缓存用而不是equals比较键。

CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用值的缓存用而不是equals比较值。

CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。

显示的清除

  • 个别清除 cache.invalidate(key)
  • 批量的清除key: cache.invalidateAll(keys)
  • 清除所有缓存 cache.invalidateAll() 不加上 keys

移除监听器

通过CacheBuilder.removeListener(RemoveListener)可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。
下面是一段使用监听器,代码

 @Test
    public void testCacheListen() throws ExecutionException, InterruptedException {
        CacheLoader loader = new CacheLoader () {
            public String load(String key) throws Exception {
                return "sailong" + new Random().nextInt(100);
            }
        };


        RemovalListener removalListener = new RemovalListener() {
            public void onRemoval(RemovalNotification removal) {
                String key = removal.getKey();
                String value = removal.getValue();
                System.out.println("移除了" + key + ": " + value);
            }
        };
        LoadingCache cache2 = CacheBuilder.newBuilder()
                .expireAfterAccess(2, TimeUnit.SECONDS)
                .removalListener(removalListener)
                .build(loader);




        System.out.println(cache2.get("haha"));
        TimeUnit.SECONDS.sleep(3);
        System.out.println(cache2.get("haha"));
    }
    

要注意,缓存不是可能并不是在数据过期之后立即执行的,如果需要我们可以创建一个自己的线程,去固定的时间间隔去,Cache.cleanUp() 或者交给ScheduledExecutorService可以很好地帮助你实现这样的功能,

你可能感兴趣的:(java,Cache)