场景
Java中基于JDK的LRU算法实现
LRU算法-缓存淘汰算法-Least recently used,最近最少使用算法
根据数据的历史访问记录来进行淘汰数据,其核心思想是:如果有数据最近被访问过,那么将来被访问的几率也更高
在Java中可以利用LinkedHashMap容器简单实现LRU算法
LinkedHashMap底层就是用的HashMap加双链表实现的,而且本身已经实现了按照访问顺序的存储。
此外,LinkedHashMap中本身就实现了一个方法removeEldestEntry用于判断是否需要移除最不常读取的数,
方法默认直接返回false,不会移除元素,因此只需要重写这个方法,可以实现当缓存满之后,就移除不常用的数据。
新建LruCache类,使其继承LinkedHashMap并重写removeEldestEntry方法
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<K,V> extends LinkedHashMap<K,V> {
private int size;
public LruCache(int size){
//accessOrder:默认为false(即默认按照插入顺序迭代)为true时(按照访问顺序迭代,支持实现LRU算法时)
//作为一般规则,默认负载因子(0.75)提供了一个很好的在时间和空间成本之间进行权衡。数值越高,空间开销,但增加查找成本.
super(size,0.75f,true);
this.size = size;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size()>size;
}
}
调用示例
LruCache<String,Integer> cache = new LruCache<>(4);
for (int i = 0; i < 10; i++) {
if(i == 5){
cache.get("2");
}
cache.put(i+"",i);
System.out.println(i+":"+cache);
System.out.println("size:"+cache.size());
}
注意上面的访问,当i == 5时,主动访问了一下2,主要是不希望淘汰掉它
运行结果
//0:{0=0}
//size:1
//1:{0=0, 1=1}
//size:2
//2:{0=0, 1=1, 2=2}
//size:3
//3:{0=0, 1=1, 2=2, 3=3}
//size:4
//4:{1=1, 2=2, 3=3, 4=4}
//size:4
//5:{3=3, 4=4, 2=2, 5=5}
//size:4
//6:{4=4, 2=2, 5=5, 6=6}
//size:4
//7:{2=2, 5=5, 6=6, 7=7}
//size:4
//8:{5=5, 6=6, 7=7, 8=8}
//size:4
//9:{6=6, 7=7, 8=8, 9=9}
//size:4
注:
博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
Java中优雅的实现代码耗时统计的方式
1、一般的写法
long start = System.currentTimeMillis();
try{
costTimeHandler();
} finally {
System.out.println("cost:"+(System.currentTimeMillis() - start));
}
优点是简单,适用范围广;缺点是侵入性强,大量的重复代码
2、使用Spring AOP实现代码耗时统计
使用Spring AOP,想要统计某个方法耗时,使用切面可以无侵入的实现。
首先定义自定义注解CountTime,声明周期是运行时,作用域是在方法上
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,统计方法执行消耗时间
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CountTime {
}
然后定义切面类CountTimeAspect,用于处理请求时的内容。
定义一个切面,然后进行环绕通知,进行方法的统计时间,进行日志的输出打印。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Slf4j
public class CountTimeAspect {
//首先定义一个切点
@org.aspectj.lang.annotation.Pointcut("@annotation(com.ruoyi.demo.actiondemo.CountTime)")
public void countTime(){
}
@Around("countTime()")
public Object doAround(ProceedingJoinPoint joinPoint){
Object obj = null;
try{
long beginTime = System.currentTimeMillis();
obj = joinPoint.proceed();
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringTypeName();
log.info("类:{},方法:{}耗时为:{}",className,methodName,System.currentTimeMillis()-beginTime);
}catch (Throwable throwable){
throwable.printStackTrace();
}
return obj;
}
}
注意这里的包路径与上面的注解的路径一致
然后在需要统计的方法上添加注解CountTime,这里统计定时任务CountTimeTask中testCountTime执行的耗时
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@EnableScheduling
public class CountTimeTask {
@Scheduled(fixedRateString = "10000")
@CountTime
public void testCountTime(){
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试以上结果
3、java中使用实现AutoCloseable接口实现方法耗时统计
在JDK1.7引入了一个新的接口AutoCloseable,通常它的实现类配合try{}使用,
try(){}执行完毕之后,会调用方法AutoCloseable#close方法。
所以写一个Cost类实现AutoCloseable接口,创建时记录一个时间,
close方法中记录一个时间,并输出时间差值,将需要统计耗时的逻辑放在try(){}代码块中。
public class Cost implements AutoCloseable{
private long start;
public Cost(){
this.start = System.currentTimeMillis();
}
@Override
public void close() throws Exception {
System.out.println("cost:"+(System.currentTimeMillis() - start));
}
}
调用示例
try(Cost cost = new Cost()){
costTimeHandler();
}
其中方法costTimeHandler()执行一个耗时操作
public static void costTimeHandler(){
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
优点是简单,使用范围广泛,且适合统一管理;缺点是依然有代码侵入。
4、也可以使用Java Agent 探针(代理)的技术来实现
在JDK1.5时,引入了java.lang.Instrument包,该包提供了一些工具帮助开发人员在java程序运行时,
动态修改系统中的Class类型。其中,使用该软件包的一个关键组件为Java Agent,它的功能更像是Class类型的转换器,
可以在运行时接收程序外部请求,对Class类型进行修改。
实现方式自行学习。