LRU缓存设计与实现详解

LRU缓存设计与实现详解

    • 一、LRU缓存核心概念
      • 1.1 LRU策略定义
      • 1.2 应用场景
      • 1.3 核心操作要求
    • 二、数据结构设计:双向链表+哈希表
      • 2.1 为什么选择双向链表?
      • 2.2 为什么结合哈希表?
      • 2.3 节点结构设计(双向链表)
      • 2.4 LRU缓存的逻辑结构
    • 三、核心算法实现
      • 3.1 初始化参数
      • 3.2 get方法实现(获取并更新顺序)
      • 3.3 put方法实现(插入/更新+淘汰策略)
      • 3.4 复杂度分析
    • 四、边界处理与优化技巧
      • 4.1 虚拟头尾节点设计
      • 4.2 容量为1的极端情况
      • 4.3 线程安全优化
    • 五、Java内置实现:LinkedHashMap
      • 5.1 利用LinkedHashMap实现LRU
      • 5.2 LinkedHashMap原理
    • 六、分布式LRU缓存设计(进阶)
      • 6.1 一致性哈希算法
      • 6.2 缓存穿透与雪崩
      • 6.3 典型架构
    • LRU总结
      • LRU的优缺点
      • 适用场景
      • 优化建议

LRU(Least Recently Used)作为最经典的缓存淘汰策略之一,广泛应用于操作系统、数据库、Web框架等场景。本文我将深入解析LRU缓存的核心原理、数据结构设计、算法实现及优化策略,结合Java代码实现,帮你掌握高性能缓存系统的设计方法。

一、LRU缓存核心概念

1.1 LRU策略定义

LRU缓存通过维护一个「最近使用」的顺序列表,当缓存容量已满时,主动淘汰最近最少使用的元素,为新元素腾出空间。其核心思想是:优先保留近期使用过的数据,淘汰长时间未访问的数据

1.2 应用场景

  • 浏览器缓存:存储最近访问的网页资源,加速页面加载
  • 数据库查询缓存:缓存高频访问的SQL结果,减少数据库压力
  • Java内存管理:JVM堆内存中的LRU机制优化对象回收
  • 分布式系统:Redis的allkeys-lru策略提升热点数据访问效率

1.3 核心操作要求

  • get(key):获取指定键的值,若存在则返回并标记为最近使用,时间复杂度O(1)
  • put(key, value):插入或更新键值对,若容量不足则淘汰最近最少使用的键,时间复杂度O(1)

二、数据结构设计:双向链表+哈希表

2.1 为什么选择双向链表?

  • 有序性维护:链表天然支持顺序访问,方便将最近使用的节点移动到头部
  • 快速删除:双向链表可在O(1)时间内删除任意节点(需前驱节点指针)
  • 高效插入:新节点始终插入头部,旧节点淘汰从尾部移除

2.2 为什么结合哈希表?

  • 快速定位:哈希表存储键到节点的映射,实现O(1)时间的存在性检查
  • 解耦数据:链表维护顺序,哈希表维护键的索引,分工明确

2.3 节点结构设计(双向链表)

class Node {int key;        // 键(用于哈希表定位)int value;      // 值Node prev;      // 前驱节点Node next;      // 后继节点public Node(int key, int value) {this.key = key;this.value = value;}
}

2.4 LRU缓存的逻辑结构

LRU缓存结构设计

哈希表 (key -> Node)↓
双向链表(头节点=最近使用,尾节点=最久未使用)
head <-> node1 <-> node2 <-> ... <-> tail

三、核心算法实现

3.1 初始化参数

import java.util.HashMap;public class LRUCache {private HashMap<Integer, Node> cache;  // 键到节点的映射private int capacity;                 // 缓存容量private Node head;                    // 最近使用节点(头节点)private Node tail;                    // 最久未使用节点(尾节点)public LRUCache(int capacity) {this.capacity = capacity;cache = new HashMap<>(capacity);// 初始化虚拟头尾节点,简化边界处理head = new Node(-1, -1);tail = new Node(-1, -1);head.next = tail;tail.prev = head;}
}

3.2 get方法实现(获取并更新顺序)

public int get(int key) {if (!cache.containsKey(key)) {return -1;}Node node = cache.get(key);// 将节点移动到头部removeNode(node);addToHead(node);return node.value;
}// 私有方法:删除任意节点
private void removeNode(Node node) {Node prevNode = node.prev;Node nextNode = node.next;prevNode.next = nextNode;nextNode.prev = prevNode;
}// 私有方法:将节点插入头部
private void addToHead(Node node) {node.next = head.next;node.prev = head;head.next.prev = node;head.next = node;
}

3.3 put方法实现(插入/更新+淘汰策略)

public void put(int key, int value) {if (cache.containsKey(key)) {// 更新值并移动到头部Node node = cache.get(key);node.value = value;removeNode(node);addToHead(node);return;}if (cache.size() == capacity) {// 淘汰尾节点(最久未使用)Node lastNode = tail.prev;cache.remove(lastNode.key);removeNode(lastNode);}// 插入新节点到头部Node newNode = new Node(key, value);cache.put(key, newNode);addToHead(newNode);
}

3.4 复杂度分析

  • 时间复杂度
    get和put操作均通过哈希表定位节点(O(1)),双向链表的删除和插入操作均为O(1),总体时间复杂度O(1)
  • 空间复杂度
    存储所有节点的空间为O(capacity),哈希表和链表的额外空间为O(capacity),总体空间复杂度O(capacity)

四、边界处理与优化技巧

4.1 虚拟头尾节点设计

  • 作用:避免处理头节点和尾节点的null指针边界问题
  • 实现:初始化两个虚拟节点,头节点的next指向最近使用节点,尾节点的prev指向最久未使用节点

4.2 容量为1的极端情况

// 测试用例:容量为1时,每次put都会淘汰旧节点
LRUCache cache = new LRUCache(1);
cache.put(1, 10);  // 缓存:{1:10}
cache.put(2, 20);  // 淘汰1,缓存:{2:20}
System.out.println(cache.get(1));  // 输出-1

4.3 线程安全优化

  • 问题:上述实现非线程安全,多线程环境下需加锁
  • 解决方案
    使用synchronized修饰get/put方法,或基于ReentrantLock实现:
private final ReentrantLock lock = new ReentrantLock();public int get(int key) {lock.lock();try {// 原有逻辑} finally {lock.unlock();}
}

五、Java内置实现:LinkedHashMap

5.1 利用LinkedHashMap实现LRU

import java.util.LinkedHashMap;public class LRUCacheJava {private LinkedHashMap<Integer, Integer> cache;private int capacity;public LRUCacheJava(int capacity) {this.capacity = capacity;// accessOrder=true表示按访问顺序排序(最近访问的在尾部)cache = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {@Overrideprotected boolean removeEldestEntry(java.util.Map.Entry<Integer, Integer> eldest) {return size() > capacity;}};}public int get(int key) {return cache.getOrDefault(key, -1);}public void put(int key, int value) {cache.put(key, value);}
}

5.2 LinkedHashMap原理

  • 数据结构:哈希表+双向链表(维护插入顺序或访问顺序)
  • 淘汰策略:重写removeEldestEntry方法,容量超标时淘汰最旧节点
  • 性能对比:比手动实现稍慢(封装带来的开销),但代码更简洁

六、分布式LRU缓存设计(进阶)

6.1 一致性哈希算法

  • 问题:分布式环境中缓存节点动态变化时的键分布问题
  • 解决方案
    通过一致性哈希将键映射到节点,节点增减时仅影响相邻节点,减少缓存失效

6.2 缓存穿透与雪崩

  • 缓存穿透:查询不存在的键导致大量数据库访问
    解决方案:缓存空值或布隆过滤器
  • 缓存雪崩:大量缓存同时失效导致请求积压
    解决方案:设置随机过期时间+多级缓存

6.3 典型架构

客户端 <-> 负载均衡 <-> 缓存集群(每个节点实现LRU)↓数据源(数据库/文件系统)

LRU总结

LRU的优缺点

优点缺点
实现相对简单无法处理突发访问模式
适合时间局部性数据对空间局部性支持差
平均性能稳定可能淘汰高频低最近使用数据

适用场景

  • 优先使用:数据访问符合时间局部性(如历史记录、用户行为数据)
  • 谨慎使用:数据访问模式频繁变化(如实时统计类数据)
  • 结合使用:与LFU(最少频率使用)策略结合,提升复杂场景性能

优化建议

  1. 预加载热点数据:初始化时加载高频访问数据
  2. 限制最大容量:避免内存溢出,结合监控动态调整容量
  3. 异步淘汰:将淘汰操作放到后台线程执行,减少主线程阻塞

That’s all, thanks for reading!
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/912336.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/912336.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ中,basicAck、basicNack和basicReject是三种核心的消息确认机制

channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); 在RabbitMQ中&#xff0…

UNIAPP入门基础

一、开发环境准备 1. 安装 HBuilderX(官方推荐IDE) 下载地址:HBuilderX 官网 版本选择: App开发版:开箱即用,内置 UniApp 插件 标准版:需手动安装 UniApp 插件(运行时会提示) 安装步骤: Windows:双击安装包,勾选“创建桌面快捷方式” macOS:拖拽到 Applications…

前端单点登录

“前端单点登录&#xff08;SSO, Single Sign-On&#xff09;”是指在多个系统之间共享用户登录状态&#xff0c;使用户只需登录一次&#xff0c;就可以在多个子系统中使用同一身份访问资源&#xff0c;无需重复登录。 以下是一个典型的前端单点登录方案的介绍和实现思路&…

DiNA:扩张邻域注意力 Transformer

摘要 Transformer 正迅速成为跨模态、跨领域和跨任务中应用最广泛的深度学习架构之一。在计算机视觉领域&#xff0c;除了持续发展的纯 transformer 架构&#xff0c;分层 transformer 也因其优越的性能和在现有框架中易于集成而受到广泛关注。这类模型通常采用局部化的注意力…

对于“随机种子”的作用的理解

深度学习系统的两大组成部分 确定性部分&#xff08;无法通过种子改变&#xff09;&#xff1a; ✅ 网络结构&#xff1a;层数、神经元数量、连接方式 ✅ 学习率&#xff1a;如您所说&#xff0c;这是开发者明确设置的固定值或调度策略 ✅ 损失函数&#xff1a;MSE、CrossEnt…

C# 委托(调用带引用参数的委托)

调用带引用参数的委托 如果委托有引用参数&#xff0c;参数值会根据调用列表中的一个或多个方法的返回值而改变。 在调用委托列表中的下一个方法时&#xff0c;参数的新值&#xff08;不是初始值&#xff09;会传给下一个方法。例如&#xff0c; 如下代码调用了具有引用参数的…

Cisco FMC events无法加载并且cpu high故障- Cisco bug

FMC故障 日志无法加载&#xff0c;并且CPU high 95% 经确认是bug问题&#xff0c;需要重置1个monetdb的进程 https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwe47671 https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwi64429 2.1 备份FMC配置 2.2 重置进程 大约为2…

HarmonyOS 公共事件机制介绍以及多进程之间的通信实现(9000字详解)

HarmonyOS 公共事件机制介绍以及多进程之间的通信 CES(Common Event Service,公共事件服务)为应用程序提供订阅、发布、退订公共事件的能力 1. 公共事件的介绍 1.1.1公共事件的分类&#xff1a;公共事件从系统的角度可以分为系统公共事件和自定义公共事件 系统公共事件&#x…

华为云Flexus+DeepSeek征文|快速搭建Dify LLM应用开发平台教程

【摘要】本文介绍基于华为云Flexus X实例快速部署Dify-LLM应用开发平台的解决方案。通过创建云服务器&#xff08;2核4G配置&#xff09;、弹性公网IP&#xff08;300Mbps带宽&#xff09;及安全组&#xff0c;实现平台私有化部署。方案提供两种计费模式&#xff08;按需197元/…

【blender】使用bpy对一个obj的不同mesh进行不同的材质贴图(涉及对bmesh的操作)

BMesh 简介 BMesh 是 Blender 中用于表示和操作网格数据的底层数据结构系统&#xff0c;它是传统网格数据结构的高级替代品。 主要特点 灵活拓扑支持&#xff1a; 支持 n-gons&#xff08;任意边数的多边形&#xff09;&#xff0c;而不仅仅是三角形和四边形允许边和顶点不属…

如何通过nvm切换本地node环境详情教程(已装过node.js更改成nvm)

针对系统已装过node环境或者第一次安装nvm环境如何切换nvm 文章目录 系列文章目录前言一、删除原有node环境二、使用步骤 1.下载nvm软件2.安装node不同版本3.使用node版本4.配置包文件、安装包、配置包环境 总结 一、删除原有node环境 1、删除之前安装的node包&#xff0c;以及…

概率论符号和公式整理

本文是由AI生成后&#xff0c;经作者优化整理的文章。个人总结&#xff0c;仅限参考&#xff01; 以下整理了概率论中的常用符号和公式表格&#xff0c;覆盖基础知识、关键定理和常用分布&#xff1a; 一、基础集合与事件符号 符号名称含义/公式说明 S S S样本空间所有可能结…

SpringSecurity是什么?

Spring Security是Spring生态中的安全框架&#xff0c;用于管理Web应用的认证与权限控制&#xff0c;支持多种登录方式并集成防护机制&#xff0c;可防范CSRF/XSS等攻击&#xff0c;保障企业级系统的安全性。 一、核心功能与定位 身份认证&#xff08;Authentication&#xff…

nt!IoSynchronousPageWrite函数分析之atapi!IdeReadWrite----非常重要

第一部分&#xff1a;预分析 1: kd> g Breakpoint 7 hit atapi!IdeReadWrite: f729cb2a 55 push ebp 1: kd> kc # 00 atapi!IdeReadWrite 01 atapi!IdeSendCommand 02 atapi!AtapiStartIo 03 atapi!IdeStartIoSynchronized 04 nt!KeSynchronizeExecuti…

软考系统架构设计师经验总结

本文目的 对参加的2025年上半年系统架构设计师考试进行总结提供一些备考思路给未来参加系统架构设计师的同学 个人背景 工作背景 本科计算机与技术&#xff08;学过一些计算机基础课程&#xff09;&#xff0c;15年毕业后从事过b端&#xff08;人群画像、营销、用户增长、硬…

Tailwind CSS工作原理

文章目录 前言1. 指令解析与 AST 操作&#x1f6a9; **核心处理流程**&#x1f9e9; **具体流程说明** 2. **配置驱动的样式生成**3. **JIT 模式&#xff08;Just-In-Time&#xff09;的核心逻辑**4. **插件与自定义扩展**5. **与 PostCSS 管道的协同**6. **优化与 Tree Shakin…

web网页开发,在线%旅游景点管理%系统demo,基于Idea,vscode,html,css,vue,java,maven,springboot,mysql

经验心得 两业务单&#xff0c;都是业务逻辑开发&#xff0c;基本crud&#xff0c;什么是前后端&#xff0c;怎么分离前后端&#xff0c;前后端怎么通讯的&#xff0c;是以什么格式进行通讯这些咱们都需要掌握&#xff0c;后面剩下就是前后端不同层如何优化。管理系统很常见了其…

面试150 长度最小的子数组

思路 联想到滑动窗口法。左窗口的值为0&#xff0c;遍历数组对数组求和&#xff0c;当数组的和大于等于target的时候&#xff0c;窗口要收缩&#xff0c;计算子数组的长度&#xff0c;并及时更新最小的长度&#xff0c;左窗口右移。 class Solution:def minSubArrayLen(self,…

Python字典的查询操作

一、前言 在 Python 中&#xff0c;字典&#xff08;dict&#xff09; 是一种非常常用的数据结构&#xff0c;以键值对&#xff08;Key-Value Pair&#xff09;形式存储数据&#xff0c;支持快速查找、插入和删除操作。 本文将系统性地介绍 Python 字典中常见的查询操作方法&…

pyhton基础【18】面向对象基础一

目录 一.面向对象 二.面向对象概述 三.类与对象 一.面向对象 Python中的面向对象编程OOP是一种编程范式&#xff0c;它使用对象来设计软件。对象是具有属性(称为属性)和可以执行的操作(称为方法)的数据结构。 基础概念 类&#xff1a;class 类是创建对象的蓝图或模板。它…