Guava 工具类之Cache的使用
一.guava cache 介绍
1.介绍
guava cache是Google guava中提供的一款轻量级的本地缓存组件,其特点是简单、轻便、完善、扩展性强,内存管理机制也相对完善。
2.使用缓存的优点
1.减少了网络调用的开销
2.减少了数据请求的序列化和反序列化
二.guava cache分类
guava cache 提供了2种类型:
Cache:创建1个缓存.
LoadingCache:它能够通过CacheLoader自发的加载缓存,当获取缓存中数据不存在时,会通过CacheLoader的load方法自动加载到缓存中(后面会进步说明)
三.Cache的创建
Guava的缓存有许多配置选项,所以为了简化缓存的创建过程,使用了Builder设计模式,而Builder使用的是链式编程的思想,也就是每次调用方法后返回的是对象本生,这样可以极大的简化配置过程。
Guava的缓存创建需要通过CacheBuilder的build()方法构建,它的每个方法都返回CacheBuilder本身,直到build方法被调用才会创建Cache或者LoadingCache。
创建过程(这里只做一个简单的创建,后面会加上各种配置项)
Cache<String, String> cache = CacheBuilder.newBuilder().build(); LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder().build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { // 缓存加载逻辑,可以通过查询数据库,获取经常访问且固定不变的数据 return null; } });
//LoadingCache在创建时需要我们添加一段缓存获取的逻辑,当我们从缓存中获取某个key对应的value时,如果缓存中没有,则会通过load(key)这个方法重新去加载这个value,当获取到value缓存并返回.
四.构建时缓存配置项的配置
Cache和LoadingCache的配置项是一样的
concurrencyLevel(int concurrencyLevel) : 设置并发级别 //cache提供了设置并发级别的api,使得缓存支持并发的写入和读取。同ConcurrentHashMap类似Guava cache的并发也是通过分离锁实现。在一般情况下,将并发级别设置为服务器cpu核心数是一个比较不错的选择。 initialCapacity(int initialCapacity):设置初始容量 //我们在构建缓存时可以为缓存设置一个合理大小初始容量。由于Guava的缓存使用了分离锁的机制,扩容的代价非常昂贵。所以合理的初始容量能够减少缓存容器的扩容次数。 maximumSize(long maximumSize):设置最大存储量 //Guava Cache可以在构建缓存对象时指定缓存所能够存储的最大记录数量。 //当Cache中的记录数量达到最大值后再调用put方法向其中添加对象,Guava会先从当前缓存的对象记录中选择一条删除掉,腾出空间后再将新的对象存储到Cache中。 expireAfterWrite(long duration, TimeUnit unit):设置写入多久的过期时间 expireAfterAccess(long duration, TimeUnit unit):设置多久没被访问(读/写)的过期时间 //在构建Cache对象时,可以通过CacheBuilder类的expireAfterAccess和expireAfterWrite两个方法为缓存中的对象指定过期时间,过期的对象将会被缓存自动删除。 //其中,expireAfterWrite方法指定对象被写入到缓存后多久过期,expireAfterAccess指定对象多久没有被访问后过期。 //可以同时用expireAfterAccess和expireAfterWrite方法指定过期时间,这时只要对象满足两者中的一个条件就会被自动过期删除。(有等验证)
//一共4种,这里介绍2种,只不过是参数类型传的不同而已
removalListener(new RemovalListener<K, V>):设置移除监听器 //可以为Cache对象添加一个移除监听器,这样当有缓存被删除时可以感知到这个事件。在RemovalListener写的是删除回调时的通知逻辑 recordStats():打开统计信息开关 //可以对Cache的命中率、加载数据时间等信息进行统计。 //在构建Cache对象时,可以通过CacheBuilder的recordStats方法开启统计信息的开关。开关开启后Cache会自动对缓存的各种操作进行统计,调用Cache的stats方法可以查看统计后的信息。 weakKeys(): //使用弱引用存储键 weakValues()://使用弱引用存储值 softValues()://使用软引用存储值 //这里的配置项会在缓存回收处讲解
五.Cache的API操作
cache.asMap(); //将缓存转换成1个ConcurrentMap
cache.cleanUp(); //清空缓存
cache.get(K key, Callable<? extends V> loader) throws ExecutionException //获取缓存,当缓存不存在时,则通Callable进行加载并返回。该操作是原子,会抛出ExecutionException异常
cache.getAllPresent(Iterable<?> keys); //通过已存在的keys集合获取到一个固定长度的map集合
cache.getIfPresent(Object key); //获取一个缓存,如果该缓存不存在则返回一个null值
cache.invalidate(Object key); //通过key使value无效
cache.invalidateAll(); //使所有的value无效
cache.invalidateAll(Iterable<?> keys); //使keys集合中对应的value无效
cache.put(String key, Object value); //向缓存中添加数据
cache.putAll(Map<? extends K, ? extends V> m); //向级存中添加Map集合
cache.size(); //缓存大小
cache.stats(); //查看缓存命中结果
六.LoadingCache的API操作
loadingCache.getUnchecked(K key); //不检查value是否存在
七.缓存的回收
在前文提到过,在构建本地缓存时,我们应该指定一个最大容量来防止出现内存溢出的情况。在guava中除了提供基于数量和基于内存容量两种回收策略外,还提供了基于引用的回收。
1.基于数量的回收
这个回收策略非常简单,我们只需指定缓存的最大存储数量maximumSize即可:
CacheBuilder.newBuilder().maximumSize(100).build(); // 缓存数量上限为100
2.基于最大容量的回收
在最大容量回收策略中,我们需要设置2个必要参数:
maximumWeigh:用于指定最大容量
Weigher:在加载缓存时用于计算缓存容量大小。
这里我们例举一个key和value都是String类型缓存:
CacheBuilder.newBuilder() .maximumWeight(1024 * 1024 * 1024) // 设置最大容量为 1M // 设置用来计算缓存容量的Weigher .weigher(new Weigher<String, String>() { @Override public int weigh(String key, String value) { return key.getBytes().length + value.getBytes().length; } }).build();
//当缓存的最大数量/容量逼近或超过我们所设置的最大值时,Guava就会使用LRU算法对之前的缓存进行回收。
3.基于引用的回收策略
强引用: 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。 Object o=new Object(); // 强引用 当内存空间不足,垃圾回收器不会自动回收一个被引用的强引用对象,而是会直接抛出OutOfMemoryError错误,使程序异常终止。 软引用: 相对于强引用,软引用是一种不稳定的引用方式,如果一个对象具有软引用,当内存充足时,GC不会主动回收软引用对象,而当内存不足时软引用对象就会被回收。 SoftReference<Object> softRef=new SoftReference<Object>(new Object()); // 软引用 Object object = softRef.get(); // 获取软引用 使用软引用能防止内存泄露,增强程序的健壮性。但是一定要做好null检测。 弱引用: 弱引用是一种比软引用更不稳定的引用方式,因为无论内存是否充足,弱引用对象都有可能被回收。 WeakReference<Object> weakRef = new WeakReference<Object>(new Object()); // 弱引用 Object obj = weakRef.get(); // 获取弱引用 guava采用可以配置弱引用和软引用的策略来让用户自行决定缓存数据的类型,这样可以防止发生内存泄露的现象
八.代码实现
package com.study; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.junit.Test; import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import com.google.common.cache.Cache;; /** * guava cache 测试 * * @author pengbo.zhao * @data 2019年11月5日 上午10:24:15 * * * cache的创建 * * {@link #createCache()} 创建一个简单的cache * * * */ public class GuavaCache { @Test public void createCache() throws ExecutionException{ Cache<String,String> cache = CacheBuilder.newBuilder() //设置并发数(以获取当前操作系统cpu数来确定并发数) .concurrencyLevel(Runtime.getRuntime().availableProcessors()) //设置初始容量 .initialCapacity(1000) //设置最大存储量 .maximumSize(900) //设置过期时间(3秒内没有使用)在指定时间内没有进行读写,会移除key,下次取的时候从loading中取 .expireAfterAccess(3,TimeUnit.SECONDS) //设置过期时间(写入3秒内过期)在一定时间内没有创建/覆盖时,会移除key,下次从loading中取 .expireAfterWrite(3, TimeUnit.SECONDS) //设置引用清除(设置弱引用存储值) .weakValues() //设置统计信息 .recordStats() //设置移除通知 .removalListener(new RemovalListener<String, String>() { @Override public void onRemoval(RemovalNotification<String, String> notification) { System.out.println(notification.getKey()+"-"+notification.getValue()+" is remove"); } }) //构建 .build(); cache.put("key1", "value1"); System.out.println(cache.getIfPresent("key1")); String key2 = cache.get("key2",new Callable<String>() { @Override public String call() throws Exception { return "value2"; } }); System.out.println(key2); } }
是在指定项在一定时间内没有创建/覆盖时,会移除该key,下次取的时候从loading中取