guava里面如何实现缓存

来源:互联网 发布:cae软件 nvh 编辑:程序博客网 时间:2024/06/13 20:38

  我们经常会遇到一些本地缓存的场景,比如遍历N条记录,依据记录里面的某个ID,获取到相应的名称,如果遍布的时候逐条去调用外部依赖的服务查询名称,显然会比较慢,如果把获取到的记录保存到本地的缓存里面,先判断本地缓存里面是否有记录存在,如果能够找到记录则直接取本地的数据。显然这样处理的话效率会提升不少。

  相对于ConcurrentMap来讲guava cache适用于:

  • 你愿意消耗一些内存空间来提升速度
  • 你预料到某些键会被查询一次以上
  • 缓存中存放的数据总量不会超出内存容量。本地单机缓存不是集中式缓存服务器。
只要你的场景符合其中的一项,那guava cache就适合你。

加载
   “获取缓存--如果没有--则计算”【get - if - absent - compute】原子语义
在调用get时传入一个Callable实例。缓存元素也可以通过Cache.put方法直接插入,但自动加载是首选。因为它更容易推断所有缓存内容的一致性。

CacheLoader
  LoadingCache是附带CacheLoader构建而成的缓存实现。创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法。
从LoadingCache查询的正规方式是使用get(K)方法。这个方法要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值。
由于CacheLoader可能抛出异常,LoadingCache.get(K)也声明为抛出ExecutionExceptino异常。
如下:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()        .maximumSize(1000)        .build(            new CacheLoader<Key, Graph>() {                public Graph load(Key key) throws AnyException {     // 手工抛异常                    return createExpensiveGraph(key);                }            });...try {
    // 也需要在get的时候抛出异常
    return graphs.get(key);} catch (ExecutionException e) {    throw new OtherException(e.getCause());}

如果你定义的CacheLoader没有声明任何检查异常,则可以通过getUnchecked(K)查找缓存;但必须注意,一旦CacheLoader声明了检查型异常,就不可以调用getUnchecked(K).

如下:
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);

Callable

    所有类型的guava cache,不管有没有自动加载功能都支持get(K,Callable(V))方法。这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。
在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法实现了这个模式:如果有缓存则返回;否则运算、缓存、然后返回。
    
public static void main(String[] args) {        String key = "instance_id";        Cache<String,Double> cache = CacheBuilder.newBuilder().maximumSize(1000).build();        int i = 1;        while (i < 5){            i++;            try {                Double value = cache.get(key, new Callable<Double>() {                    @Override                    public Double call() throws Exception {                        System.out.println("hello");  //第一次没有的时候会运算然后添加到缓存里面。后面就直接从缓存里面读取数据.                        return 120d;                    }                });                System.out.println(value);            } catch (ExecutionException ex) {                System.out.println(ex.getCause());            }        }    }
输出:
hello120.0120.0120.0120.0
相对于像Cache.asMap().putIfAbsent(K,V)来讲,Cache.get(K,Callable<V>)应该最优先使用。

缓存回收机制
提供了三种回收方式:基于容量回收、定时回收、基于引用回收
基于容量回收
需要使用CacheBuilder.maximumSize(long). 

显式清除
个别清除:Cache.invalidate(key)
批量清除:Cache.invalidateAll(keys)
清除所有缓存项:Cache.invalidateAll()

在JDK8里面的实现代码
static Map<String, String> cache2 = new ConcurrentHashMap<>();    public static String getAppProduct(String appName){        // call aone        System.out.println("call aone");        return "hello1";    }    public static void main(String[] args) {        String key = "instance_id";        String result = cache2.computeIfAbsent(key,(item) -> getAppProduct(key));        System.out.println(result);

其实我觉得用jdk8的这种方式也是可以的.