第5天持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
Guava Cache 介绍
- Google Guava Cache 是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。基于容量的方式内部实现采用LRU算法,基于引用回收很好的利用了Java虚拟机的垃圾回收机制。
- Guava Cache 与 ConcurrentMap 很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。 Guava Cache 为了限制内存占用,通常都设定为自动回收元素。
- 适用场景
- 愿意消耗一些内存空间来提升速度
- 预料到某些键会被多次查询
- 缓存中存放的数据总量不会超出内存容量
- Guava Cache是运行在JVM的本地缓存,并不能把数据存放到外部服务器上。如果有这样的要求,因该尝试Memcached或Reids这类分布式缓存。
Guava Cache 加载
加载方式1- CacheLoader
loadingCache 是附带CacheLoader 构建而成的缓存实现。创建自己的CacheLoader 通常只需要简单地实现 V load(K key) throws Exception 方法。
/**
* @author nicole
* @version 1.0.0
* @Description: 加载方式 CacheLoader
* * 加载方式1:CacheLoader
* * 1.设置缓存容量
* * 2.设置超时时间
* * 3.提供移除监听器
* * 4.提供缓存加载器
* * 5.构建缓存
* @date 2021/12/13
* @Modify by nicole
*/
public class GuavaCacheDemo_1 {
public static void main(String[] args) {
//监听器
RemovalListener<String, String> listener = new RemovalListener<String, String>() {
@Override
public void onRemoval(RemovalNotification<String, String> removalNotification) {
System.out.println("监听器移除的 [ key = "+removalNotification.getKey()+ ",value = " + removalNotification.getValue()+"]");
};
};
//缓存加载器
CacheLoader<String,String> loader = new CacheLoader<String, String>() {
@Override
public String load(String s) throws Exception {
Thread.sleep(1000);
if( "key".equals(s)){
return null;
}
System.out.println("缓存加载器key = "+ s + "is loading in CacheLoad");
return "缓存加载器的key是 " + s;
}
};
//提供缓存加载器
LoadingCache<String,String> testCache = CacheBuilder.newBuilder()
// 设置缓存大小
.maximumSize(5)
//设置过期时间,单位是分钟
.expireAfterWrite(10,TimeUnit.MINUTES)
.expireAfterAccess(10,TimeUnit.MINUTES)
//提供移除监听器
.removalListener(listener)
//提供缓存加载器
.build(loader);
//构建缓存
for (int i = 0; i < 10; i++) {
String key = "key" + i;
String value = "value" + i;
testCache.put(key,value);
System.out.println("["+key + ":"+ value +"]"+" is put in cache ");
}
//如果存在就获取
System.out.println( "打印 key6 = "+ testCache.getIfPresent("key6"));
try {
System.out.println( "打印不存在的key "+testCache.get("key"));
} catch (Exception e) {
e.printStackTrace();
System.out.println("不存在的key,会报错");
}
}
}
//控制台执行结果如下图。
Guava Cache 缓存回收
- 回收方式1- 基于容量回收
maximumSize(long) : 当缓存中的元素超过指定值时。
- 回收方式2- 定时回收
expireAfterAccess(long , TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。
- 回收方式3 - 基于引用回收(Reference - based Eviction)
-
CacheBuilder.weakKeys() : 使用弱引用存储键 。当key没有其他引用时,缓存项可以被垃圾回收。
-
CacheBuilder.weakValues() : 使用弱引用存储值 。当value没有其他引用时,缓存项可以被垃圾回收。
-
CacheBuilder.softValues() : 使用软引用存储键 ,按照全局最近最少使用的顺序回收。
/**
-
@author nicole
-
@version 1.0.0
-
@Description:
-
@date 2021/12/15
-
@Modify by nicole
-
- 加载方式2:Callable
-
- 所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K, Callable)方法。
-
- 这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。
-
- 在整个加载方法完成前,缓存项相关的可观察状态都不会更改。
-
- 这个方法简便地实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。 */ public class GuavaCacheDemo_2 { //构建容器为3的缓存对象 static Cache<String, String> testCache = CacheBuilder.newBuilder().maximumSize(3).build();
public static void main(String[] args) { testCache.put("1234","我是存在的"); // 如果存在就获取,不存在返回null System.out.println("获取key6的值为" + testCache.getIfPresent("key6"));
try { // 获取key为123的缓存数据,如果有就返回,没有就返回call方法的返回值 System.out.println(testCache.get("123", new Callable<String>() { @Override public String call() throws Exception { return "计算、缓存、啥啥啥"; } })); // 获取key为1234的缓存数据,如果有就返回,没有就返回call方法的返回值。注意这里key是存在的 System.out.println(testCache.get("1234", new Callable<String>() { @Override public String call() throws Exception { return "我是来打酱油的"; } })); } catch (ExecutionException e) { e.printStackTrace(); }
}
} //控制台执行结果如下: 获取key6的值为null 计算、缓存、啥啥啥 我是存在的
-
Guava Cache 显示清除
任何时候,你都可以显式地清除缓存项,而不是等到它被回收:
- 个别清除:Cache.invalidate(key)
- 批量清除:Cache.invalidateAll(keys)
- 清除所有缓存项 :Cache.invalidateAll()
Guava Cache统计
CacheBuilder.recordStats() : 用来开启Guava Cache 的统计功能。统计打开后, Cache.stats()方法会返回CacheStats 对象以提供如下统计信息:
-
hitRate() : 缓存命中率;
-
averageLoadPenalty() : 加载新值的平均时间,单位为纳秒;
-
evictionCount() : 缓存项被回收的总数,不包括显式清除;
/**
-
统计
-
@author nicole
-
@version 1.0.0
-
@Description:
-
@date 2021/12/15
-
@Modify by nicole */ public class GuavaCacheDemo_3 { static Cache<String,Object> testCache = CacheBuilder.newBuilder() // 当值没有其它(强或软)引用时,缓存项可以被垃圾回收 .weakValues() // 开启Guava Cache的统计功能 .recordStats() .build(); public static void main(String[] args) { testCache.put("1234",new String("123"));
// 主动gc System.gc(); System.out.println(testCache.getIfPresent("1234")); /* stats()方法会返回CacheS tats 对象以提供如下统计信息: hitRate():缓存命中率; averageLoadPenalty():加载新值的平均时间,单位为纳秒; evictionCount():缓存项被回收的总数,不包括显式清除。 */ System.out.println(testCache.stats());
} }
//控制台输出 null CacheStats{hitCount=0, missCount=1, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
-
Ehcache
Ehcache是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
主要特性
- 快速、简单、支持多种缓存策略
- 支持内存和磁盘缓存数据,因此无需担心容量问题
- 缓存数据会在虚拟机重启的过程中写入磁盘
- 可以通过RMI、可插入API等方式进行分布式缓存(比较弱)
- 具有缓存和缓存管理器的侦听接口
- 支持多缓存管理器实例,以及一个实例的多个缓存区域
- 提供Hibernate的缓存实现
适用场景
- 单个应用或者对缓存访问要求很高的应用
- 简单的共享可以,但是不适合涉及缓存恢复、大数据缓存
- 大型系统,存在缓存共享、分布式部署、缓存内容大不适合使用
- 在实际工作中,更多是将Ehcache作为与Redis配合的二级缓存
Caffeine
Caffeine 是Google基于Java8对GuavaCache的重写升级版本,支持丰富的缓存过期策略,尤其是TinyLfu淘汰算法,提供了一个近乎最佳的命中率。从性能上(读、写、读/写)也足以秒杀其他一堆进程内缓存框架。Spring5更是直接放弃使用了多年的Guava,而采用了Caffeine。
Caffeine的API 的操作功能和Guava 是基本保持一致的,并且Caffeine为了兼容之前是Guava的用户,做了一个Guava的Adapter给大家使用也是十分的贴心。
Caffeine是一个非常不错的缓存框架,无论是在性能方面,还是在API方面,都比Guava cache要优秀一些。如果在新的项目中要使用local cache 的话,可以优先考虑使用Caffeine。对于老的项目,如果使用了Guava cache ,想要升级为Caffeine的话,可以使用Caffeine提供的Guava cache适配器,方便的进行切换。
继续看代码 ,三种加载使用方式。