高级java每日一道面试题-2024年9月30日-算法篇-LRU是什么?如何实现?
如果有遗漏,评论区告诉我进行补充
面试官: LRU是什么?如何实现?
我回答:
LRU(Least Recently Used)是一种常用的缓存淘汰策略,用于在缓存满时决定哪些数据应该被移除。LRU算法的基本思想是:当缓存达到其容量上限时,最近最少使用的数据会被优先淘汰。这种策略假设最近使用的数据在未来也会被频繁访问。
LRU算法概述
LRU算法是一种缓存淘汰策略,其核心思想是:如果一个数据在最近一段时间没有被访问到,那么在未来被访问的可能性也很小。因此,当缓存空间已满时,LRU算法会选择最近最少使用的数据进行淘汰。
LRU算法广泛应用于操作系统中的页面置换、数据库查询优化、Web缓存等场景,是最大化缓存命中率的有效手段之一。
LRU算法的实现原理
LRU的实现
LRU的实现通常需要一个数据结构来同时支持快速查找和插入/删除操作。常用的数据结构是哈希表(HashMap)和双向链表(Doubly Linked List)的结合体。
数据结构
- 哈希表:用于快速查找缓存中的元素。
- 双向链表:用于维护元素的访问顺序,最近访问的元素放在链表头部,最久未访问的元素放在链表尾部。
基本操作
-  插入: - 如果新插入的键已经在缓存中,则更新其值,并将其移动到链表头部。
- 如果新插入的键不在缓存中,且缓存已满,则移除链表尾部的元素,并将新元素插入到链表头部。
 
-  访问: - 如果访问的键在缓存中,则将其移动到链表头部。
- 如果访问的键不在缓存中,则返回null或其他默认值。
 
-  删除: - 如果删除的键在缓存中,则从链表和哈希表中移除该元素。
- 如果删除的键不在缓存中,则不进行任何操作。
 
LRU算法的实现需要满足以下几个要求:
- 查找快:能够迅速找到缓存中的数据。
- 插入快:能够快速地将新数据插入到缓存中。
- 删除快:能够高效地删除缓存中的数据。
- 维护顺序:需要维护数据的访问顺序,以便在缓存空间不足时淘汰最近最少使用的数据。
代码实现
下面是一个使用Java实现LRU缓存的示例:
import java.util.HashMap;
import java.util.Map;public class LRUCache<K, V> {private final int capacity;private final Map<K, Node<K, V>> map;private final DoublyLinkedList<K, V> list;public LRUCache(int capacity) {this.capacity = capacity;this.map = new HashMap<>();this.list = new DoublyLinkedList<>();}public V get(K key) {if (map.containsKey(key)) {Node<K, V> node = map.get(key);list.moveToHead(node); // 将访问的节点移到链表头部return node.value;}return null;}public void put(K key, V value) {if (map.containsKey(key)) {Node<K, V> node = map.get(key);node.value = value; // 更新节点的值list.moveToHead(node); // 将更新的节点移到链表头部} else {if (map.size() >= capacity) {Node<K, V> removedNode = list.removeTail(); // 移除链表尾部的节点map.remove(removedNode.key); // 从哈希表中移除对应的键}Node<K, V> newNode = new Node<>(key, value);list.addHead(newNode); // 将新节点添加到链表头部map.put(key, newNode); // 在哈希表中添加新的键值对}}private static class Node<K, V> {K key;V value;Node<K, V> prev;Node<K, V> next;Node(K key, V value) {this.key = key;this.value = value;}}private static class DoublyLinkedList<K, V> {private Node<K, V> head;private Node<K, V> tail;public void addHead(Node<K, V> node) {if (head == null) {head = tail = node;} else {node.next = head;head.prev = node;head = node;}}public void moveToHead(Node<K, V> node) {if (node == head) return; // 如果节点已经是头结点,则无需移动removeNode(node);addHead(node);}public Node<K, V> removeTail() {if (tail == null) return null;Node<K, V> node = tail;removeNode(tail);return node;}private void removeNode(Node<K, V> node) {if (node.prev != null) {node.prev.next = node.next;} else {head = node.next;}if (node.next != null) {node.next.prev = node.prev;} else {tail = node.prev;}node.prev = null;node.next = null;}}public static void main(String[] args) {LRUCache<Integer, String> cache = new LRUCache<>(2);cache.put(1, "one");cache.put(2, "two");System.out.println(cache.get(1)); // 输出: onecache.put(3, "three"); // 移除最近最少使用的 2System.out.println(cache.get(2)); // 输出: nullcache.put(4, "four"); // 移除最近最少使用的 1System.out.println(cache.get(1)); // 输出: nullSystem.out.println(cache.get(3)); // 输出: threeSystem.out.println(cache.get(4)); // 输出: four}
}
解释
-  LRUCache 类: - capacity:缓存的最大容量。
- map:哈希表,用于存储键和对应的节点。
- list:双向链表,用于维护节点的访问顺序。
 
-  get 方法: - 如果键存在于缓存中,将对应的节点移动到链表头部,并返回其值。
- 如果键不存在于缓存中,返回null。
 
-  put 方法: - 如果键已经存在于缓存中,更新其值并将节点移动到链表头部。
- 如果键不存在于缓存中且缓存已满,移除链表尾部的节点,并将新节点添加到链表头部。
- 如果键不存在于缓存中且缓存未满,直接将新节点添加到链表头部。
 
-  Node 类: - 表示双向链表中的一个节点,包含键、值以及前驱和后继指针。
 
-  DoublyLinkedList 类: - 实现了双向链表的基本操作,包括添加节点到头部、移动节点到头部、移除节点等。
 
LRU算法的性能分析
LRU算法的性能主要取决于哈希表和双向链表的操作效率。由于哈希表的查找、插入和删除操作的时间复杂度都是O(1),双向链表的插入、删除和移动操作的时间复杂度也都是O(1)(在已知节点位置的情况下),因此LRU算法的整体时间复杂度可以认为是O(1)。
然而,需要注意的是,在实际应用中,由于哈希表的冲突和链表节点的移动等操作,LRU算法的实际性能可能会受到一定影响。此外,当缓存数据量很大时,哈希表和链表的内存开销也需要考虑。
LRU算法的改进和优化
针对LRU算法的不足,有一些改进和优化方法:
- LRU-K算法:将“最近使用过1次”的判断标准扩展为“最近使用过K次”,以减少缓存污染问题。LRU-K算法需要多维护一个队列来记录所有缓存数据被访问的历史。
- Two Queues(2Q)算法:使用两个缓存队列,一个是FIFO队列,一个是LRU队列。新数据先放入FIFO队列,当数据再次被访问时,将其移到LRU队列。这种算法结合了FIFO和LRU的优点。
- MQ算法:根据访问频率将数据划分为多个队列,不同的队列具有不同的访问优先级。新数据放入最低优先级的队列,当数据的访问次数达到一定次数时,将其提升到更高优先级的队列。
总结
综上所述,LRU算法是一种高效且广泛应用的缓存淘汰策略。在Java中,可以通过使用哈希表和双向链表的组合来实现LRU缓存。同时,也需要根据实际应用场景和需求对LRU算法进行改进和优化。
相关文章:
高级java每日一道面试题-2024年9月30日-算法篇-LRU是什么?如何实现?
如果有遗漏,评论区告诉我进行补充 面试官: LRU是什么?如何实现? 我回答: LRU(Least Recently Used)是一种常用的缓存淘汰策略,用于在缓存满时决定哪些数据应该被移除。LRU算法的基本思想是:当缓存达到其容量上限时࿰…...
 
CSS选择器的全面解析与实战应用
CSS选择器的全面解析与实战应用 一、基本选择器1.1 通配符选择器(*)2.标签选择器(div)1.3 类名选择器(.class)4. id选择器(#id) 二、 属性选择器(attr)三、伪…...
vue3自动暴露element-plus组件的ref
自动暴露子组件的方法,注意在TS下,需要自己声明类型,我这里全用any代替了 <template><el-button click"getFocus">获得焦点</el-button><com ref"comRef" /> </template><script setup…...
龙芯+FreeRTOS+LVGL实战笔记(新)——10蜂鸣器嘀嘀嘀
本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了完善与优化,各位可以先到本人主页下去浏览另一专栏的博客列表(目前已撰写36篇,图1所示),再决定是否订阅。此外,也可以…...
 
微信小程序-数据模型与动态赋值
首先新建一个小程序项目. 这边有创建基础项目的流程:从0新建一个微信小程序实现一个简单跳转_小白开发小程序源代码-CSDN博客 一共两步: 1.建立页面的 数据模型 和 默认赋值: 默认赋值: 2.接收输入框的新文案,动态替换上面的文案展示 //文件 testUI.js增加方法:onInputChan…...
 
【Redis】Linux下安装配置及通过C++访问Redis
文章目录 一、Linux Centos 7.0版本下的安装及配置二、通过C访问Redis 一、Linux Centos 7.0版本下的安装及配置 通过源来安装,此次安装的版本为 redis 5.0 的,要通过其他源进行安装,首先安装 scl 源 yum install centos-release-scl-rh再安…...
Python 入门教程(4)数据类型 | 4.7、元组
文章目录 一、元组1、定义2、创建3、访问元组元素4、遍历元组5、 前言: 在Python编程中,元组(tuple)是一种内置的数据结构,它提供了一种存储多个项目(元素)的方式,这些项目可以是不同…...
 
Temu正在吸引越来越多的亚马逊卖家,这个市场Temu蝉联下载榜首
近年来,全球电商市场竞争愈发激烈,各大平台纷纷使出浑身解数,以期在激烈的市场竞争中脱颖而出。 一个来自中国的新兴电商平台——Temu,凭借其独特的市场策略和迅猛的发展势头,正在吸引越来越多的亚马逊卖家。Temu为美国…...
设计原则模式概览
前言 架构设计是软件系统稳定的核心因素,也是程序员晋级架构师的核心因素,建议日常开发过程中针对设计进行深挖与思考 核心 分清楚哪些是稳定的,哪些是变化的(一定有稳定跟变化的成分); 捋清楚哪些是类设计…...
高级主题:接口性能测试与压力测试
在现代软件开发中,确保接口的性能和稳定性是非常重要的。随着用户数量的增加,接口需要能够承受高并发请求,从而保证良好的用户体验。本篇文章将介绍如何使用 Python 工具 Locust 进行接口性能测试和压力测试,分析测试结果…...
 
python绘制图像
柱状图 import os# 输入想要存储图像的路径 os.chdir(D:)import matplotlib.pyplot as plt import numpy as np # 改变绘图风格 import seaborn as snssns.set(color_codesTrue)cell [gen7, xgspon, 3081GB, vettel, totalplay, other] pvalue [21, 20, 18, 13, 7, 34]width…...
 
如何修复变砖的手机并恢复丢失的数据
您可能之前听说过“变砖”,但您知道什么是变砖手机吗?正如许多论坛中经常提出的问题一样,我如何知道我的手机是否变砖了?好吧,手机变砖主要有两种类型,即软件变砖和硬变砖。软变砖手机意味着重启后您仍然可…...
服务器使用了代理ip,遇到流量攻击,会对服务器有影响吗
当服务器使用代理IP并遭遇流量攻击(如DDoS攻击)时,仍然会对服务器产生影响。以下是关于这种情况的一些详细分析: 1. 流量攻击的性质 流量攻击的目的是通过发送大量请求来耗尽目标服务器的资源或带宽,导致服务中断或不…...
 
从存储到人工智能洞察: 利用 MinIO 和 Polars 简化数据管道
将 MinIO 的高性能、可扩展企业对象存储的强大功能与 Polars(闪电般快速的 DataFrame 库)的快速内存数据处理功能相结合,可以显著提高数据管道的性能。在 AI 工作流中尤其如此,其中预处理大型数据集和执行特征选择是关键步骤。在这…...
只需要 1 分钟语音数据实现声音克隆
只需要 1 分钟语音数据实现声音克隆 GPT-SoVITS 是一个基于少量语音数据(1 分钟左右)即可训练出高质量 TTS(文本转语音)模型的开源项目,提供少样本语音克隆能力。目前该开源项目已经获得了 33.2k 的 Star!…...
 
OpenEuler虚拟机安装保姆级教程 | 附可视化界面
0x00 系统介绍 在 2019 年 7 月 19 日,华为宣布要在年底正式开源 openEuler 操作系统;在半年后的 12 月 31 日,华为正式开源了 openEuler 操作系统,邀请社区开发者共同来贡献。 一年后,截止到 2020 年12 月 25日&…...
 
表格控件QTableWidget
下面说一下表格的常用方法 行列数目、行表头、列表头 行表头:就是表格控件的第一行,用于设置每一列的标题 列表头:就是表格控件的第一列,用于设置每一行的标题,通常缺省则默认显示行号 设置和获取行列的数目 在添…...
LeetCode236题:二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖…...
虚谷中使用PL/SQL改变模式下所有表的大小写
一、将表名转换为小写 1、原理和思路 首先,我们需要查询出指定模式下的所有表名,在xugu中,数据字典dba_tables包含了当前库下的所有表信息,我们可以使用游标(CURSOR)来遍历这些表名。 2、代码示例如下&am…...
数据挖掘的基本步骤和流程解析:深入洞察与策略实施
一、引言 在数据时代的浪潮中,数据挖掘技术已成为企业洞察市场、优化运营和驱动创新的利器。 它融合了统计学、机器学习、数据库管理和人工智能等领域的先进技术,旨在从海量数据中 提取有价值的信息。 本文将深入探讨数据挖掘的六个基本步骤,…...
 
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
 
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
 
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
 
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
 
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
 
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
