LRU缓存淘汰算法实现方案,这次没人再说你不会开发
我们在平时项目开发中是不是会遇到这样的场景,每次访问的时候需要去取内存里的数据,没取到就添加到内存,但是,又不想取到的内存是过于陈旧的。那这块我们该怎么去设计算法,从而很合理的去管理我们内存的数据呢?
今天,给大家分享一个在我们开发中比较常用的缓存淘汰算法 LRU,LRU是指最近最少使用策略来管理内存数据。根据数据的历史访问记录来进行淘汰缓存,即假如数据最近被访问过,那么它以后被访问到的几率会更高,也就不会被淘汰。
01
如何实现LRU缓存淘汰算法
场景:
我们现在有这么个真实场景,我在爬取某个网站时,控制该网站的代理IP并发数,太多会搞垮对方网站的对吧,要蹲号子的呢。这里我需要维护一个代理IP代理池,而且这些IP肯定不是一直都很稳定的,但是又不能取一个就丢一个,这样太浪费资源。所以我会将这些IP缓存起来,进行按需提取,采用LRU最近最少使用的策略去管理代理IP。
实现方案
核心思想:我们维护一个有序的链表,然后在链表尾部的数据就是最早被访问过的数据,当有新的数据被访问时,就从链表的表头开始顺序遍历访问。
1,当访问的代理IP不在链表内时,就会添加到链表头部。如下,维护了一个6个节点的链表,这6个proxyIp在链表内都没有被缓存过。
2,当链表缓存满时,此时我在访问一个新的代理proxy_ip7的时候,就会就最近很少使用的表尾的proxy_ip1节点删除掉,然后,在将当前proxy_ip7添加到表头。
3,当访问的代理ip已经在缓存中时,如,proxy_ip5已经缓存过,我们将其遍历出来,然后将其所在节点删除,最后再将其插入到链表的表头。
具体链表实现方案就是这三个步骤,就能轻松实现LRU算法,下面我用java语言将其实现一遍代码的全过程,帮助大家更好的理解和使用,其他语言就根据上面实现方案是一样的哈。
对了,Redis的缓存过期的实现也是用的LRU缓存淘汰的策略,所以你看这个算法的重要性了吧
核心算法类:
public final class LRUCacheUtil {
private LRUCacheUtil() {
}
/**
*
* 获取指定大小的,同步的,LRU缓存Map
*
* @param <K>
* @param <V>
* @param capacity
* @return
*/
public static <K, V> Map<K, V> getLRUCache(final int capacity) {
Map<K, V> LRUMap = Collections.synchronizedMap(new LinkedHashMap<K, V>(capacity, 0.75F, true) {
private static final long serialVersionUID = 352094108437285631L;
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
});
return LRUMap;
}
}
再写一个测试类使用下:
public class LRUCacheTest {
private static Map<String, Long> ipFetchTimeMap = LRUCacheUtil.getLRUCache(6);
public static void main(String[] args) {
long baseTime = System.currentTimeMillis();
ipFetchTimeMap.put("proxy_ip1", baseTime+100);
ipFetchTimeMap.put("proxy_ip2", baseTime+200);
ipFetchTimeMap.put("proxy_ip3", baseTime+300);
ipFetchTimeMap.put("proxy_ip4", baseTime+400);
ipFetchTimeMap.put("proxy_ip5", baseTime+500);
ipFetchTimeMap.put("proxy_ip6", baseTime+600);
//遍历链表缓存数据
System.out.println("缓存中存在数据:");
for(Map.Entry<String, Long> entry : ipFetchTimeMap.entrySet()) {
System.out.println(entry.getKey());
}
ipFetchTimeMap.put("proxy_ip7", baseTime+600);
//缓存满时,再添加新的数据
System.out.println("缓存满时,再添加新的数据,缓存中的数据为:");
for(Map.Entry<String, Long> entry : ipFetchTimeMap.entrySet()) {
System.out.println(entry.getKey());
}
//访问缓存中已有的数据
ipFetchTimeMap.get("proxy_ip5");
System.out.println("访问缓存中已有的数据proxy_ip5,缓存数据为:");
for(Map.Entry<String, Long> entry : ipFetchTimeMap.entrySet()) {
System.out.println(entry.getKey());
}
}
}
我们再来看看运行的结果是不是和我们上面方案一样(由下往上看哈),可以回去对比方案图看看:
总结,今天我们将开发中最长遇到也是非常重要的LRU缓存淘汰算法做了详细的讲解以及具体代码实现,主要是采用维护一个链表的方案进行开发的,当然,这个方案也不是最优的,它的时间复杂度是O(n),但是这个是目前最常用的方案,我们也一直使用这个。也可以采用其他的方案,如果大家有什么好的方案,欢迎给出来更多方案,帮助我们一起学习下,毕竟,我们的初衷就是共同学深学透技术。
如果大家喜欢,或是对大家有帮助,欢迎关注我哈。
下一篇预告:数据库读写分离方面的解决方案
关于架构师修炼
本号旨在分享一线互联网各种技术架构解决方案,分布式以及高并发等相关专题,同时会将作者的学习总结进行整理并分享。
更多技术专题,敬请期待
- css hover对其包含的元素进行样式设置
- JSON 数据使用方法
- Eureka Server之间的注册表信息同步
- .NET Core 观察者模式 以及 delegate 和 event
- webapp设置适应pc和手机的页面宽高以及布局层叠图片文字
- 比较Spring AOP与AspectJ
- Promise原理解析与实现
- Spring Cloud 覆写远端的配置属性
- C语言心得一
- .NET Core装饰模式和.NET Core的Stream
- Nginx反向代理,负载均衡,redis session共享,keepalived高可用
- 多行图片hover加边框兼容IE7+
- Debian JDK安装及配置
- Python With-As
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 剑指offer:重建一个二叉树
- Redis基础数据类型(string、hash、list)
- 【go】编程之法:01背包问题及滚动数组优化
- 【go】剑指offer:求一个数的整数次方
- 《编程珠玑》字符串包含
- redis基本数据类型(集合、HyperLogLog、地理位置)
- 【go】剑指offer:不同程序员遇到相同的题
- Spring全家桶之SpringSecurity
- Go实现字符串全排列字典序排列详解
- Go实现字符串全排列详解递归
- springboot整合RSA进行sign签名校验
- Go寻找最长回文字符串——中心扩展法
- Spring高级技术应用——百战商城实现(上)
- 基于Springboot+jpa+thymeleaf+rabbit+SpringBoot mail 的简单项目
- 【go】剑指offer: 删除链表结点O(1)时间复杂度