Spring面试3.0
SpringAOP的顺序
- Aop的注解
@Before
@After
@AfterReturning
@AfterThrowing
@Around
Spring4 正常
Spring 从4到5底层的aop发生了变化
Spring4异常
正常的执行顺序是这样的
Spring5和Spring4的对比
Spring的循环依赖
什么是Spring的循环依赖
说明:
多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A
说明图:
两种注入方式对循环依赖有什么影响
构造方法注入和set方法的注入
构造方法注入对循环依赖不友好
结论:
我们AB循环依赖问题只要A的注入方式是setter且singleton, 就不会有循环依赖问题
spring容器循环依赖报错演BeanCurrentlylnCreationException
ServiceA
import org.springframework.stereotype.Component;
@Component
public class ServiceA {
private ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
ServiceB
import org.springframework.stereotype.Component;
@Component
public class ServiceB {
private ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
ClientConstructor
一直无限的循环下去
下面是set方法来进行注入
ServiceA
import org.springframework.stereotype.Component;
@Component
public class ServiceA {
private ServiceB serviceB;
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
System.out.println("A 里面设置了B");
}
}
ServiceB
import org.springframework.stereotype.Component;
@Component
public class ServiceB {
private ServiceA serviceA;
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("B 里面设置了A");
}
}
ClientSet
public class ClientSet {
public static void main(String[] args) {
//创建serviceA
ServiceA serviceA = new ServiceA();
//创建serviceB
ServiceB serviceB = new ServiceB();
//将serviceA注入到serviceB中
serviceB.setServiceA(serviceA);
//将serviceB注入到serviceA中
serviceA.setServiceB(serviceB);
}
}
加上Spring容器过后两者的效果
单例模式支持循环依赖
多例模式不支持循环依赖
xml的配置
xml version="1.0" encoding="UTF-8"?>
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
id="a" class="com.hhf.study.spring.circulardepend.A" >
name="b" ref="b"/>
id="b" class="com.hhf.study.spring.circulardepend.B">
name="a" ref="a"/>
log4j.properties
### 设置###
log4j.rootLogger = debug,stdout,D,E
### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r
ClientSpringContainer
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
* Error creating bean with name 'a': 578624778
* Requested bean is currently in creation: Is there an unresolvable circular reference?
*
*
* 只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,因为单例的时候只有一份,随时复用,那么就放到缓存里面
* 而多例的bean,每次从容器中荻取都是—个新的对象,都会重B新创建,
* 所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
*/
public class ClientSpringContainer {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A a = context.getBean("a",A.class);
B b = context.getBean("b",B.class);
}
}
改变成多例模式过后
异常
Spring 通过建立三级缓存的方式来解决循环依赖
DefaultSingletonBeanRegistry
第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象(成品)
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)(半成品)
第三级缓存: Map> singletonFactories,存放可以生成Bean的工厂(准备生产的原料)
讲解源码
1.实例化
内存中申请一块空间,租赁好了房子东西还没搬进去
然后进行初始化属性填充完成属性的赋值(决定该拿啥进去)
2.三级缓存+四大方法
1.getSingleton:希望从容器里面获得单例的bean,没有的话 2.doCreateBean: 没有就创建bean(三级缓存)
3.populateBean: 创建完了以后,要填充属性
4.addSingleton: 填充完了以后放到一级缓存,再添加到容器进行使用
第一层singletonObjects存放的是已经初始化好了的Bean,
第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean
循环依赖的Spring用三级缓存解决的步骤
1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A
然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
Bebug的使用方法
打断点的时候尽量不要将程序打通
debug的技巧
这只是在debug的细节方面的操作
- (Step Over)f6代表是单步,一步步走
- (Step into)f5代表是源码天生的自然进入,这是打debug本身JDK自带的源码,源码里面没有你自己所写的代码
- (Force Step Into)Alt+Shift+F7 这个是强制进入,这个一般用来debug强制进入自己所写的源代码
- 一般源码级别的调试, (Step into)f5足够了,但如果你要用(Force Step Into)Alt+Shift+F7 也完全可以。
- 如果找不到刚才停留的那一行的debug,如果断点打飞了怎么办,这个时候请选择Show Execution Point Alt+F10(也叫归位)
- 有用的就进去,没用的就跳出去
Spring的debug过程
特点:
就是找到程序输出的前一行代码并且打一个断点,然后继续运行一直找到程序运行结束的前一行代码,并且进入到源代码里面去一直到不能进入的时候就完成(目的就是看这个代码运行的时候经过了那些的过程和代码的流程)
Spring利用三级缓存来解决Spring循环依赖的全过程
debug图片
简短的语言说明全过程
1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A
然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
和老师的对比一下
redis
版本的升级说明
五大数据类型应用的情况在什么时候可以用到7
redis6.0.8Linux版本性能最好最稳定 redis.io官网地址
五大基本类型是怎么用的
细节:命令不区分大小写,而key区分大小写
help@类型名词 就是查看命令用户帮助手册
- String :
set key value
get key
同时设置多个键值
MSET key value [key value …]
获取多个键值
MGET key [key …]
增加步长 INCR key
INCRBY key increment
递减数值
DECR key
减少指定的整数
DECRBY key decrement
获取字符串的长度
STRLEN key
用于分布式锁
setnx key value 没有我创建有我不创建
set key value [Ex seconds][PX milliseconds][NX|XX]
EX:key在多少秒之后过期
PX:key在多少毫秒之后过期
NX:当key不存在的时候,才创建key,效果等同于setnx
XX:当key存在的时候,覆盖key
列子
应用场景
商品编号,订单号采用INCR命令生成 INCR items:001 对001号的商品进行点赞点一下加一个1 get items:001是统计点赞的人数或者说是热度
喜欢的文章
2.hash
对应的java的数据是Map<Strinhg ,Map<Object,Object>>
设置一个字段的值
HSET key field value
获取一个字段的值
HGET key field
一次设置多个字段的值
HMSET key field value [field value …]
一次获取多个字段的值
HMGETkey field [field …]
获取所有字段值
hgetall key
获取某个key内的全部数量
hlen
删除一个key
hdel
应用场景:
购物车中小厂
3.list
向列表左边添加元素:LPUSH key value [value …]
向列表右边添加元素:RPUSH key value [value …]
查看列表:LRANGE key start stop
获取列表中元素的个数:LLEN key
应用场景:
4.set
添加元素:SADD key member[member …]
删除元素:SREM key member [member …]
获取集合中的所有元素:SMEMBERS key
判断元素是否在集合中:SISMEMBER key member
获取集合中的元素个数:SCARD key
从集合中随机弹出一个元素元素不删除:SRANDMEMBER key [数字]
从集合中随机弹出一个元素弹出一个删除一个:SPOP key[数字]
集合运算:
集合的差集运算A-B:属于A但不属于B的元素构成的集合
SDIFF key [key …]
集合的交集运算A∩B:属于A同时也属于B的共同拥有的元素构成的集合:SINTER key [key …]
集合的并集运算AUB:属于A或者属于B的元素合并后的集合
SUNION key [key …]
应用背景:
微信抽奖小程序:1 用户ID,立即参与按钮
sadd key 用户ID
2 显示已经有多少人参与了,上图23208人参加
SCARD key
3 抽奖(从set中任意选取N个中奖人)
SRANDMEMBER key 2 随机抽奖2个人,元素不删除
SPOP key3 随机抽奖3个人,元素会删除
微信朋友圈点赞
pub是消息朋友圈,msgID:点赞的用户ID
微博好友关注社交关系
QQ内推可能认识的人
5.zset
添加元素:ZADD key score member [score member …]
按照元素分数从小到大的顺序 返回索引从start到stop之间的所有元素
ZRANGE key start stop [WITHSCORES]
获取元素的分数: ZSCORE key member
删除元素: ZREM key member [member …]
获取指定分数范围的元素: ZRANGEBYsCORE key min max [WITHSCORES] [LIMIT offset count]
增加某个元素的分数: ZINCRBY key increment member
获取集合中元素的数量:ZCARD key
获得指定分数范围内的元素个数:9zCOUNT key min max
按照排名范围删除元素:ZREMRANGEBYRANK key start stop
获取元素的排名:从小到大:ZRANK key member
从大到小:ZREVRANK key member
运用背景1:根据商品销售对商品进行排序显示
思路:定义商品销售排行榜(sorted set集合),key为goods:sellsort,分数为商品销售数量。
商品编号1001的销量是9,商品编号1002的销量是15
zadd goods:sellsort 9 1001 15 1002
有一个客户又买了2件商品1001,商品编号1001销量加2
zincrby goods:sellsort 2 1001
求商品销量前10名
ZRANGE goods:sellsort 0 10 withscores
运用背景2:抖音热搜
redis之分布式锁
锁的场景:
JVM层面的锁,单机版的锁
分布式微服务架构,拆分后各个微服务之间为了避免冲突和数据故障而加入的一种锁,分布式锁
mysql
zookeeper
redis
锁的应用场景
一般的互联网公司,大家都习惯用redis做分布式的锁
redis–》redlock–》redisson–》 lock/unlock
分布式锁
分布式锁需要注意的问题
商品超买
docker启动redis
docker exec -it redis redis-cli
秒杀超买的demo演示4
环境的搭建
分布式锁1:在高并发和多线程的影响下这段代码有什么问题?
String result = stringRedisTemplate.opsForValue().get("goods:001");//看看库存的数量够不够
int goodNumber = result == null ? 0 : Integer.parseInt(result);
if (goodNumber > 0) {
int realNumber = goodNumber - 1;
//有商品获取结果存回redis
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort);
return "成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort;
} else {
System.out.println("商品已经售完" + serverPort);
}
return "商品已经售完" + serverPort;
1,2两行存在没有原子性所以在多线程和高并发的状态下会出现超买的现象 单机版会存在的问题 synsynchronize(关键字) 和ReentrantLock(类)
synsynchronize:不见不散:就是里面的线程不释放会造成线程积压一大堆的线程卡在外面(加上synsynchronize代码块包裹代码)
ReentrantLock:过期不候:lock.trylock()设置时间过期过后就不候
分布式锁第二:分布式微服务架构有什么问题
Nginx的轮循操作
在高并发的环境下出现商品超买,就是一个优惠券卖了两次但是只支付了一次的钱公司的利益就亏空了(买一赠一)
jemter进行压测出现严重的商品超买现象
解决:分布式锁的命令setnx
加锁成功返回true 失败返回false
3.0加了setnx
谁进来谁加锁
//保证当前的线程的名字是不一样的
String value=UUID.randomUUID().toString()+Thread.currentThread().getName();
//flag 加锁成功为true则不进去 不成功为false则进去
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
//为false抢锁失败 则true进入 返回抢锁失败
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");//看看库存的数量够不够
int goodNumber = result == null ? 0 : Integer.parseInt(result);
if (goodNumber > 0) {
int realNumber = goodNumber - 1;
//有商品获取结果存回redis
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort);
stringRedisTemplate.delete(REDIS_LOCK);//解锁
return "成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort;
} else {
System.out.println("商品已经售完" + serverPort);
}
return "商品已经售完" + serverPort;
4.0:1.0出现异常后可能无法释放锁必须要在代码层面finally释放锁,要保证lock和unlock必须要同时保证调用
5.0部署了微服务jar包的机器宕机了代码根本执行不到finally这块没办法保证解锁,这个key没有被删除,需要加入一个过期时间限定key到时间就删除
6.0:5.0问题加锁和设置过期时间要是原子性操作才可以
7.0在业务层面的话如果出现远程调用其他的微服务调用的时间大于当前业务锁的失效的临界时间,这个时候当前业务锁删除,但是当前的业务线程还在执行,执行完成删除锁,但是当前业务的锁已经过期,新的线程进来执行业务获取锁这时候当前的业务线程会删除掉新线程的锁(key)即张冠李戴删除别人的锁
public String buy_Goods() {
谁进来谁加锁
//保证当前的线程的名字是不一样的
String value=UUID.randomUUID().toString()+Thread.currentThread().getName();
try {
//flag 加锁成功为true则不进去 不成功为false则进去
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK,value,10L,TimeUnit.SECONDS);
//为false抢锁失败 则true进入 返回抢锁失败
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");//看看库存的数量够不够
int goodNumber = result == null ? 0 : Integer.parseInt(result);
if (goodNumber > 0) {
int realNumber = goodNumber - 1;
//有商品获取结果存回redis
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort);
return "成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort;
} else {
System.out.println("商品已经售完" + serverPort);
}
return "商品已经售完" + serverPort;
} finally {
//保证删除的时候是自己的锁
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
stringRedisTemplate.delete(REDIS_LOCK);//解锁
}
}
}
8.0:在7.0的时候在finally块中判断和删除不是原子的操作
判断加锁和解锁的客户端不是一个客户端
lua脚本保证原子性删除
不用lua脚本你还有什么想法
可以用Redis自身的事务
redis事务的说明
8.1
finally {
//保证删除的时候是自己的锁
// if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
// stringRedisTemplate.delete(REDIS_LOCK);//解锁
// }
while (true){
//乐观锁我希望我删除的时候别人不动 如果别人动了,我再来尝试删除直到删除到我自己为止开启监控
stringRedisTemplate.watch(REDIS_LOCK);
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value));
{
//是否支持事务
stringRedisTemplate.setEnableTransactionSupport(true);
//开启事务
stringRedisTemplate.multi();
//删除有可能成功有可能失败 删除的返回值是key的数量是保存到一个对列里面
stringRedisTemplate.delete(REDIS_LOCK);
//提交 相当于存到对列里面
List<Object> list = stringRedisTemplate.exec();
if (list == null) {
continue;
}
}
stringRedisTemplate.unwatch();
break;
}
}
8.2用lua脚本的处理
finally {
Jedis jedis= RedisUtils.getJedis();
String script="if redis.call('get',KEYS[1]) == ARGV[1]"+
"then"+
"return redis.call('del',KEYS[1])"+ "else"+
" return 0"+
"end";
try {
//相当于exec过后返回key的数量,有返回就解锁没有就解锁不成功
Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
if ("1".equals(o.toString())){
System.out.println("-----del redis lock ok ");
}else {
System.out.println("-----del redis lock error" +
" ");
}
} finally {
if (null!=jedis){
jedis.close();
}
}
}
确保redisLock过期时间大于业务执行时间的问题?分布式情况下的远程调用会导致业务时间大于过期时间。
redis如何进行续期?
9.0
redis集群+CAP对比zookeeper
redis
AP:redis异步复制造成的锁丢失, 比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了。保证数据的高可用性而牺牲数据的一致性
主节点获取就返回
自己写的redis在redis集群的环境下不OK所以才用RedLock之Redisson落地的实践
zookiper
CP:保证数据的一致性牺牲数据的高可用性
主节点和其他的一起获取到再返回
Redisson
使用优化
谁进来谁加锁
//保证当前的线程的名字是不一样的
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
///++++++++
RLock redissonLock = redisson.getLock(REDIS_LOCK);
//+++++++++++++
redissonLock.lock();
try {
String result = stringRedisTemplate.opsForValue().get("goods:001");//看看库存的数量够不够
int goodNumber = result == null ? 0 : Integer.parseInt(result);
if (goodNumber > 0) {
int realNumber = goodNumber - 1;
//有商品获取结果存回redis
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort);
return "成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort;
} else {
System.out.println("商品已经售完" + serverPort);
}
return "商品已经售完" + serverPort;
} finally {
//+++++++++
redissonLock.unlock();
}
}
9.1版本
容易报锁找不到的异常
public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY);
redissonLock.lock();
try{
String result = stringRedisTemplate.opsForValue().get("goods:001");
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");
System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort);
return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort;
}else {
System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort);
}
return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort;
}finally {
//还在持有锁的状态,并且是当前线程持有的锁再解锁
if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){
redissonLock.unlock();
}
}
}
分布式锁的总结
自动延迟机制
看门狗的机制:
客户端1加锁的锁key默认生存时间才30秒,如果超过了30秒,客户端1还想一直持有这把锁,怎么办呢?
简单!只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。
总结:
redis缓存过期淘汰策略
redis内存满了怎么办?
redis.conf的配置文件
内存的默认
一般配置redis设置内存为最大物理内存的四分之三
设置redis的内存大小
命令窗口修改
查看生产上的配置内存的大小info memory
redis打满了怎么办
结论:没有加上过期时间就会导致数据写满maxmemory 为了避免类似情况,引出下一章内存淘汰策略
缓存过期淘汰策略
淘汰的策略的类型
默认使用的策略类型是noeviction不再驱逐
redis过期键的删除策略
如果一个键是过期的过期之后是不是马上就被删除呢?
如果不是到底是什么时候被删除?是什么操作
定时删除
总结:对CPU不友好,用处理器性能换取存储空间(拿时间换空间)
懒性删除
总结:对memory不友好,用存储空间换取处理器性能(拿空间换时间)
取二者的折中:定期删除
仍然有漏洞:
1 定期删除时,从来没有被抽查到
2 惰性删除时,也从来没有被点中使用过
上述2步骤======> 大量过期的key堆积在内存中,导致redis内存空间紧张或者很快耗尽
必须要有一个更好的兜底方案…
即内存淘汰策略
内存淘汰策略
LRU:最近最少使用 时间上
LFU:最近最少使用 频率上
一般用allkeys-lru
怎么配置和修改呢?
命令:
设置,查看 ,查看内存信息
配置文件:redis.conf
lru算法
解释:LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,
选择最近最久未使用的数据予以淘汰。
来源:来源
思考
查询用hash+增删用链表
比如说当有人要调用时key2时直接将key2拉动到前面去
手写lru
案例1LinkedHashMap 依赖jdk
public class LRUCacheDemoK,V> extends LinkedHashMapK, V> {
private int capacity;//缓存坑位
public LRUCacheDemo(int capacity) {
super(capacity,0.75F,false);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.EntryK, V> eldest) {
return super.size() > capacity;
}
public static void main(String[] args) {
LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);
lruCacheDemo.put(1,"a");
lruCacheDemo.put(2,"b");
lruCacheDemo.put(3,"c");
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(4,"d");
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(3,"c");
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(3,"c");
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(3,"c");
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(5,"x");
System.out.println(lruCacheDemo.keySet());
}
}
案例二 不依赖jdk
代码
//map负责查找,构建一个虚拟的双向链表,它里面安装的就是一个个Node节点,作为数据载体。
//1.构造一个node节点作为数据载体
class NodeK, V>
{
K key;
V value;
NodeK,V> prev;
NodeK,V> next;
public Node(){
this.prev = this.next = null;
}
public Node(K key, V value)
{
this.key = key;
this.value = value;
this.prev = this.next = null;
}
}
//2 构建一个虚拟的双向链表,,里面安放的就是我们的Node
class DoubleLinkedListK, V>
{
NodeK, V> head;
NodeK, V> tail;
public DoubleLinkedList(){
head = new Node();
tail = new Node();
head.next = tail;
tail.prev = head;
}
//3. 添加到头
public void addHead(NodeK,V> node)
{
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
//4.删除节点
public void removeNode(NodeK, V> node) {
node.next.prev = node.prev;
node.prev.next = node.next;
node.prev = null;
node.next = null;
}
//5.获得最后一个节点
public Node getLast() {
return tail.prev;
}
}
private int cacheSize;
Map,Node,Integer>> map;
DoubleLinkedList,Integer> doubleLinkedList;
public LRUCacheDemo(int cacheSize)
{
this.cacheSize = cacheSize;//坑位
map = new HashMap();//查找
doubleLinkedList = new DoubleLinkedList();
}
public int get(int key){
if (!map.containsKey(key)){
return -1;
}
Node, Integer> node = map.get(key);
doubleLinkedList.removeNode(node);
doubleLinkedList.addHead(node);
return node.value;
}
public void put(int key, int value)
{
if (map.containsKey(key)){ //update
Node, Integer> node = map.get(key);
node.value = value;
map.put(key, node);
doubleLinkedList.removeNode(node);
doubleLinkedList.addHead(node);
}else {
if (map.size() == cacheSize) //坑位满了
{
Node,Integer> lastNode = doubleLinkedList.getLast();
map.remove(lastNode.key);
doubleLinkedList.removeNode(lastNode);
}
//新增一个
Node, Integer> newNode = new Node(key, value);
map.put(key,newNode);
doubleLinkedList.addHead(newNode);
}
}
public static void main(String[] args) {
LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);
lruCacheDemo.put(1,1);
lruCacheDemo.put(2,2);
lruCacheDemo.put(3,3);
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(4,1);
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(3,1);
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(3,1);
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(3,1);
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(5,1);
System.out.println(lruCacheDemo.map.keySet());
}
}
/**
* true
* [1, 2, 3]
* [2, 3, 4]
* [2, 4, 3]
* [2, 4, 3]
* [2, 4, 3]
* [4, 3, 5]
* */
/**
[1, 2, 3]
[2, 3, 4]
[2, 3, 4]
[2, 3, 4]
[2, 3, 4]
[3, 4, 5]
*/