Skip to content

Latest commit

 

History

History
98 lines (87 loc) · 4.43 KB

缓存.md

File metadata and controls

98 lines (87 loc) · 4.43 KB

缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。

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

下面是使用范例.

//缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
LoadingCache<Integer, Student> studentCache
        //CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
        = CacheBuilder.newBuilder()
        //设置并发级别为8,并发级别是指可以同时写缓存的线程数
        .concurrencyLevel(8)
        //设置写缓存后8秒钟过期
        .expireAfterWrite(8, TimeUnit.SECONDS)
        //设置缓存容器的初始容量为10
        .initialCapacity(10)
        //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
        .maximumSize(100)
        //设置要统计缓存的命中率
        .recordStats()
        //设置缓存的移除通知
        .removalListener((notification) ->
            System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause())
        )
        //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
        .build(
                new CacheLoader<Integer, Student>() {
                    @Override
                    public Student load(Integer key) throws Exception {
                        System.out.println("load student " + key);
                        Student student = new Student();
                        student.setId(key);
                        student.setName("name " + key);
                        return student;
                    }
                }
        );

studentCache.put(1, new Student());

for (int i = 0; i < 20; i++) {
    //从缓存中得到数据,由于我们没有设置过缓存,所以需要通过CacheLoader加载缓存数据
    Student student;
    try {
        student = studentCache.get(1);
        System.out.println(student);

        //休眠1秒
        TimeUnit.SECONDS.sleep(1);
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

System.out.println("cache stats:");
//最后打印缓存的命中率等 情况
System.out.println(studentCache.stats().toString());

输出结果如下:

Student{id=0, name='null'} Student{id=0, name='null'} Student{id=0, name='null'} Student{id=0, name='null'} Student{id=0, name='null'} Student{id=0, name='null'} Student{id=0, name='null'} Student{id=0, name='null'} 1 was removed, cause is EXPIRED load student 1 Student{id=1, name='name 1'} Student{id=1, name='name 1'} Student{id=1, name='name 1'} Student{id=1, name='name 1'} Student{id=1, name='name 1'} Student{id=1, name='name 1'} Student{id=1, name='name 1'} Student{id=1, name='name 1'} 1 was removed, cause is EXPIRED load student 1 Student{id=1, name='name 1'} Student{id=1, name='name 1'} Student{id=1, name='name 1'} Student{id=1, name='name 1'} cache stats: CacheStats{hitCount=18, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=1794547, evictionCount=2}

guava缓存过期时间分为两种,一种是从写入时开始计时,一种是从最后访问时间开始计时,而且guava缓存的过期时间是设置到整个一组缓存上的; 这和EHCache,redis,memcached等不同,这些缓存系统设置都将缓存时间设置到了单个缓存上。

guava缓存设计成了一组对象一个缓存实例,这样做的好处是一组对象设置一组缓存策略,你可以根据不同的业务来设置不同的缓存策略,包括弱引用, 软引用,过期时间,最大项数等。另外一点好处是你可以根据不同的组来统计缓存的命中率,这样更有意义一些。

这样做也是有缺点的,缺点是首先是每个缓存组都需要声明不同的缓存实例,具体到业务程序中可能就是每个业务对象一个缓存了。 这样就把不同的业务缓存分散到不同的业务系统中了,不太好管理。