场景
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
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
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类型进行修改。
实现方式自行学习。