缓存在很多场景下都是相当有用的。例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存。
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缓存设计成了一组对象一个缓存实例,这样做的好处是一组对象设置一组缓存策略,你可以根据不同的业务来设置不同的缓存策略,包括弱引用, 软引用,过期时间,最大项数等。另外一点好处是你可以根据不同的组来统计缓存的命中率,这样更有意义一些。
这样做也是有缺点的,缺点是首先是每个缓存组都需要声明不同的缓存实例,具体到业务程序中可能就是每个业务对象一个缓存了。 这样就把不同的业务缓存分散到不同的业务系统中了,不太好管理。