Guava Cache深入理解,缓存策略解析

版权声明:如需转载,请标明转载出处哦! https://blog.csdn.net/Z0157/article/details/81570193

Guava Cache
一、Guava Cache 理解
    开发中我们对一些访问非常频繁的数据,数据量可控,为了减少网络和硬盘io消耗,减少时间成本的消耗,我们通常会选择使用缓存来提高程序访问数据的性能,减少网络IO开销。
    缓存大体分为2种:
      (1)、集中式缓存。    
      (2)、分布式缓存。
而Guava就是集中式线程安全的本地缓存。Guava Cache简单的说是一个类似ConcurrentMap<K,V>缓存数据的集合,他有自己的缓存处理策略,自动回收,自动清除,自动加载等。
       Guava缓存是单个应用中的本地缓存。它不会将数据存储到文件中,或者外部服务器;
二、Guava的使用
    1、创建方式
        (1)、Callable 
        (2)、LoadingCache
    2、两种创建方式比较:
        相同点:两种方式同样按照获取缓存-如果没有-则计算(get-if-absent-compute)的缓存规则对缓存数据进行的处理的。

          不同点:在缓存中没有得到value的时候,CacheLoader会定义一个比较宽泛的、统一的根据key值load value的方法,而Callablee的方式较为灵活,允许你在get的时候指定call。
     3、缓存的回收策略:基于容量回收、定时回收和基于引用回收
            (1)、maximumSize(long):设置容量大小,超过就开始回收。

如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项;不同的缓存项有不同的“权重”(weights);
            (2)、expireAfterWrite(long, TimeUnit):在这个时间段内没有被读/写访问,就会被回收。

CacheBuilder提供两种定时回收的方法:  expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请 注意这种缓存的回收顺序和基于大小回收一样。 expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
            (3)、expireAfterAccess(long, TimeUnit):在这个时间段内没有被写访问,就会被回收; 

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

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

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

                   CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存 同样用==而不是equals比较值。
            
     4、常用API使用
    LoadingCache:
    (1)、get(K):使用这个方法要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值。由于CacheLoader可能抛出异常,LoadingCache.get(K)也声明为抛出ExecutionException异常。
            如果你定义的CacheLoader没有声明任何检查型异常,则可以通过 getUnchecked(K) 查找缓存;但必须注意,一旦CacheLoader声明了检查型异常,就不可以调用getUnchecked(K)。

    (2)、getIfPresent(key):从现有的缓存中获取,如果缓存中有key,则返回value,如果没有则返回null
  (3)、getAll(Iterable<? extends K>)方法用来执行批量查询。默认情况下,对每个不在缓存中的键,getAll方法会单独调用CacheLoader.load来加载缓存项。可以通过重写load()方法来提高加载缓存的效率;

        
    Callable:
    (1)、get(K, Callable<V>):这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。

*******put等等其他的就不一一去介绍了。。。具体使用看我贴出来的代码

扫描二维码关注公众号,回复: 3103761 查看本文章


    ----------------
    公共(列举一部分)
       (1)、Cache.invalidate(key) :个别清除
    (2)、Cache.invalidateAll(keys):批量清除
    (3)、Cache.invalidateAll():清除所有缓存项
    (4)、removalListener(RemovalListener监听事件,在元素被删除,回收时,进行监听
    (5)、CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回CacheStats对                   象以提供如下统计信息:

                hitRate():缓存命中率;

                averageLoadPenalty():加载新值的平均时间,单位为纳秒;

                evictionCount():缓存项被回收的总数,不包括显式清除。

代码测试案例

package cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * Description:
 * User: ZhuRong
 * Date: 2018-08-10  9:34
 */
public class LoadingCacheTest {

    private static LoadingCache<Integer, String> cacheMap = CacheBuilder.newBuilder()
            .maximumSize(3) //缓存大小
            .expireAfterAccess(5,TimeUnit.SECONDS) //加载缓存之后,多久过期
//            .expireAfterWrite(1115,TimeUnit.SECONDS)//缓存被写入,update之后,多久过期
            .recordStats().build(new CacheLoader<Integer, String>() {
                //当本地缓存命没有中时,调用load方法获取结果并将结果缓存
                public String load(Integer key) throws Exception {
                    //"缓存没了,请去数据库查找";
                    System.out.println("load():key = "+key);
                    if(cacheMap.getIfPresent(key) == null){
                        return getDbResultInfo(key);
                    }else{
                        return cacheMap.getIfPresent(key);
                    }
                }

                private String getDbResultInfo(int key) throws Exception {
                    System.out.println("正在查询...");
                        cacheMap.put(key,"这是数据库查询的结果");
                        return cacheMap.getIfPresent(key);
                }
            });

    public static void main(String[] args) throws ExecutionException {
        //超过上限的时候,后面覆盖前面的key
        System.out.println("cacheMap size:"+cacheMap.size());
        cacheMap.put(1,"a");
        cacheMap.put(2,"b");
        cacheMap.put(3,"c");
        cacheMap.put(4,"d");
        System.out.println("cacheMap size:"+cacheMap.size());
        //缓存中如果有这个key返回value,如果没有这个key直接返回null
        System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(1));
        System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(2));
        System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(3));
        System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(4));
        System.out.println("--------------------------------------");

        try {
            System.out.println("第一次查询:"+cacheMap.get(1));
            System.out.println("第二次查询:"+cacheMap.get(1));
            System.out.println("第三次查询:"+cacheMap.get(1)); //这个时候key=2被key=1覆盖
            System.out.println("cacheMap size:"+cacheMap.size());
            System.out.println("--------------------------------------");
            System.out.println("第一次查询:"+cacheMap.get(3));
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(5));
        System.out.println("cacheMap get:"+cacheMap.get(5));
        System.out.println("cacheMap get:"+cacheMap.get(5));
        System.out.println("---------------后面是过期了的数据打印-------------------------------");
        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //目前其实是key =3,4,5 过期
        System.out.println("cacheMap size:"+cacheMap.size());
//        System.out.println(cacheMap.getIfPresent(4)); //说明过期的缓存是没有清楚的,而是被标识无法读取
        System.out.println(cacheMap.getIfPresent(6));
        System.out.println("cacheMap size:"+cacheMap.size());
        System.out.println(cacheMap.getIfPresent(5));
        System.out.println("cacheMap size:"+cacheMap.size());
        System.out.println(cacheMap.getIfPresent(6));
        System.out.println("cacheMap size:"+cacheMap.size());

        cacheMap.put(1,"aaaa");

        System.out.println("cacheMap size:"+cacheMap.size());


    }
}

打印结果:

看到结果是不是想到了什么?在什么情况下缓存会被清空?过期了缓存真的被清空了吗?缓存满了,后面继续put进去又会发生什么情况

仔细对着代码和输出结果你就恍然大悟了!哈哈,我就不说了,自己去运行研究,更加深理解!

package cache;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

/**
 *
 *
 * Description:
 * User: ZhuRong
 * Date: 2018-08-10  11:06
 */
public class CallCacheTest {

    public static Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();


    public static Object get(String key){
        String result = null;
        try {
            result = cache.get(key, new Callable<String>() {
                public String call() {
                    System.out.println("call() key");
                    return "result";
                }
            });
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return result;
    }

    public static void main(String[] args) {
        cache.put("key","value");
        System.out.println(get("key"));
        System.out.println(cache.getIfPresent("key"));
        System.out.println(get("key1"));

    }

}

结果输出:


     


        


 

猜你喜欢

转载自blog.csdn.net/Z0157/article/details/81570193
今日推荐