什么是缓存呢?我们先来看一个例子
实现一个斐波那契数列--递归
public static int fbnq(int n){
if(n==1 || n ==2){
return 1;
}else{
return fbnq(n-2)+fbnq(n-1);
}
}
如果我们使用debug调试进入就会发现,递归的方式需要反复计算一些值,比如fbnq(3)重复执行。每次都要重新计算,这样效率很低,于是我们想到何不把静态结果保存起来,下次用到直接取就好了,改进代码如下:
static Map map = new HashMap<>();//定义map存放每次计算的值 键是n 值是fbnQ(n)
public static int fbnq(int n) {
if(n==1 || n==2) {
return 1;
}
Integer value = map.get(n);
if(value == null) {
value = fbnq(n - 1) + fbnq(n - 2);
map.put(n, value);
}
return value;
}
在使用debug调试,就会发现每值只需要计算一次,这样效率明显提高了,这段代码中map集合充当的就是一个缓存的角色。它将每次计算的值存下来,下一次如果需要就用重新计算,只需要从 map缓存中根据键 n 取值就好了。
LRU:最近最少使用,即最少使用的会被从缓存中移除。
举个例子:比如依次往缓存中存入键为1 2 3 4 5,那么如果此时内存不够,优先移除的元素就是1,因为他距离使用最远。
但如果在插入第6个元素之前执行过类似于查找 ---> get(1) 这类操作,也就是说1这个键最近被使用了,那么如果内存不够,需要移除元素,此时队列为 2 3 4 5 1,很显然2应该被移除。这个类似于队列,从左到右,先进先出。
上面的HashMap作为缓存并不是最好的实现,缓存是存在内存中的,所以说不能无限使用,必须定义一个限制,当达到什么条件,必须清除一些缓存。这个时候采用LinkedHashMap比较合适。
LinkedHashMap中有一个构造方法:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
第一个参数是初始化容量,第二个参数是负载因子,最后一个参数是控制是否开启元数放入集合的顺序,默认是false,也就是
accessOrder=false //表示按顺序排列
accessOrder=true //即如果遇到get(key)方法则,调整元素在集合中的顺序, 最新访问的排在最后
简单实现代码如下:
LinkedHashMap map = new LinkedHashMap(16, 0.75f, true){
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
// 希望集合中放5个元素, 超过5个要移除最远使用的元素
if(this.size() <= 5) {
return false;
} else {
return true;
}
}
};
LinkedHashMap的 removeEldestEntry() 方法可以保证满足一定条件的时候返回true,触发remove集合元素
ehcache是本地缓存(类似于单机模式),如果是分布式缓存一般采用redis
添加依赖
org.ehcache
ehcache
3.6.3
javax.cache
cache-api
配置缓存文件
//文件中可以定义多个缓存,所以需要用alias来区分
java.lang.String
java.lang.String
60
60
200
注意
// 1. 类对象.getResource() 获取类路径上的一个资源文件(url对象)
URL url = GunsApplication.class.getResource("/ehcache.xml");
// 2. 准备配置对象
XmlConfiguration config = new XmlConfiguration(url);
// 3. 创建一个新的缓存管理器类
CacheManager cacheManager = CacheManagerBuilder.newCacheManager(config);
// 4. 初始化缓存管理器
cacheManager.init();//一定要初始化,否则会报错。
// 5. 获取缓存对象
Cache cache = cacheManager.getCache("cache1", String.class, String.class);
// 6. 存储键值
cache.put("xi,an", "西安");
// 7. 根据键获取值
System.out.println(cache.get("xi,an"));
// 8. 删除键
cache.remove("xi,an");
System.out.println(cache.get("xi,an"));
// 9. 清空缓存
cache.clear();
application.yml配置:
spring:
cache:
jcache:
config: classpath:ehcache.xml
记得加入classpath:表示ehcache.xml在根路径下
@EnableCaching
将@EnableCaching定义在SpringBoot启动类上,相当于启用缓存功能,内部就会创建 CacheManager(注意是spring框架的), 并交给spring容器管理。
在需要开启缓存的方法上方加上:@Cacheable(cacheNames="缓存名(在配置文件中定义的)",key = 与配置文件中的value相对(类型) )
注意key=的写法一般写字符串类型 如:
key = "'dept_' + #deptId" (springEL表达式)
除此之外:@CacheEvict ,写在需要删除缓存的方法上,用来删除某个key value,或者清空整个缓存, 用在执行了数据的增删改时,这三种情况下,都应该让缓存失效。
1) 先访问的是设置缓存类的代理对象 (由于cglib 生成该类的子类对象作为代理对象)。
2) 代理对象重写了目标方法, 在该方法内, 通过缓存管理器(cache)找对应缓存名称的缓存。
3) Cache.get(key)去获取value, 第一次访问value为空, 执行的是原本的方法。
4) 该类的方法返回结果作为value放入缓存中。
5) 第二次访问的时候,先访问的是该类的代理对象。
6) 代理对象重写了目标方法,方法内部,通过缓存管理器(cacheManager)找到对应缓存。
7) Cache.get(key)去获取对应value, 返回value不为空,直接返回缓存中的value, 没有执行原本的方法,而是执行代理对象中的目标方法。
缓存一般使用在读多写少的程序中。