当前位置: 首页 > news >正文

高级java每日一道面试题-2024年9月30日-算法篇-LRU是什么?如何实现?

如果有遗漏,评论区告诉我进行补充

面试官: LRU是什么?如何实现?

我回答:

LRU(Least Recently Used)是一种常用的缓存淘汰策略,用于在缓存满时决定哪些数据应该被移除。LRU算法的基本思想是:当缓存达到其容量上限时,最近最少使用的数据会被优先淘汰。这种策略假设最近使用的数据在未来也会被频繁访问。

LRU算法概述

LRU算法是一种缓存淘汰策略,其核心思想是:如果一个数据在最近一段时间没有被访问到,那么在未来被访问的可能性也很小。因此,当缓存空间已满时,LRU算法会选择最近最少使用的数据进行淘汰。

LRU算法广泛应用于操作系统中的页面置换、数据库查询优化、Web缓存等场景,是最大化缓存命中率的有效手段之一。

LRU算法的实现原理

LRU的实现

LRU的实现通常需要一个数据结构来同时支持快速查找和插入/删除操作。常用的数据结构是哈希表(HashMap)和双向链表(Doubly Linked List)的结合体。

数据结构
  • 哈希表:用于快速查找缓存中的元素。
  • 双向链表:用于维护元素的访问顺序,最近访问的元素放在链表头部,最久未访问的元素放在链表尾部。
基本操作
  1. 插入

    • 如果新插入的键已经在缓存中,则更新其值,并将其移动到链表头部。
    • 如果新插入的键不在缓存中,且缓存已满,则移除链表尾部的元素,并将新元素插入到链表头部。
  2. 访问

    • 如果访问的键在缓存中,则将其移动到链表头部。
    • 如果访问的键不在缓存中,则返回null或其他默认值。
  3. 删除

    • 如果删除的键在缓存中,则从链表和哈希表中移除该元素。
    • 如果删除的键不在缓存中,则不进行任何操作。

LRU算法的实现需要满足以下几个要求:

  1. 查找快:能够迅速找到缓存中的数据。
  2. 插入快:能够快速地将新数据插入到缓存中。
  3. 删除快:能够高效地删除缓存中的数据。
  4. 维护顺序:需要维护数据的访问顺序,以便在缓存空间不足时淘汰最近最少使用的数据。

代码实现

下面是一个使用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}
}

解释

  1. LRUCache 类

    • capacity:缓存的最大容量。
    • map:哈希表,用于存储键和对应的节点。
    • list:双向链表,用于维护节点的访问顺序。
  2. get 方法

    • 如果键存在于缓存中,将对应的节点移动到链表头部,并返回其值。
    • 如果键不存在于缓存中,返回null。
  3. put 方法

    • 如果键已经存在于缓存中,更新其值并将节点移动到链表头部。
    • 如果键不存在于缓存中且缓存已满,移除链表尾部的节点,并将新节点添加到链表头部。
    • 如果键不存在于缓存中且缓存未满,直接将新节点添加到链表头部。
  4. Node 类

    • 表示双向链表中的一个节点,包含键、值以及前驱和后继指针。
  5. DoublyLinkedList 类

    • 实现了双向链表的基本操作,包括添加节点到头部、移动节点到头部、移除节点等。

LRU算法的性能分析

LRU算法的性能主要取决于哈希表和双向链表的操作效率。由于哈希表的查找、插入和删除操作的时间复杂度都是O(1),双向链表的插入、删除和移动操作的时间复杂度也都是O(1)(在已知节点位置的情况下),因此LRU算法的整体时间复杂度可以认为是O(1)。

然而,需要注意的是,在实际应用中,由于哈希表的冲突和链表节点的移动等操作,LRU算法的实际性能可能会受到一定影响。此外,当缓存数据量很大时,哈希表和链表的内存开销也需要考虑。

LRU算法的改进和优化

针对LRU算法的不足,有一些改进和优化方法:

  1. LRU-K算法:将“最近使用过1次”的判断标准扩展为“最近使用过K次”,以减少缓存污染问题。LRU-K算法需要多维护一个队列来记录所有缓存数据被访问的历史。
  2. Two Queues(2Q)算法:使用两个缓存队列,一个是FIFO队列,一个是LRU队列。新数据先放入FIFO队列,当数据再次被访问时,将其移到LRU队列。这种算法结合了FIFO和LRU的优点。
  3. MQ算法:根据访问频率将数据划分为多个队列,不同的队列具有不同的访问优先级。新数据放入最低优先级的队列,当数据的访问次数达到一定次数时,将其提升到更高优先级的队列。

总结

综上所述,LRU算法是一种高效且广泛应用的缓存淘汰策略。在Java中,可以通过使用哈希表和双向链表的组合来实现LRU缓存。同时,也需要根据实际应用场景和需求对LRU算法进行改进和优化。

http://www.lryc.cn/news/450403.html

相关文章:

  • CSS选择器的全面解析与实战应用
  • vue3自动暴露element-plus组件的ref
  • 龙芯+FreeRTOS+LVGL实战笔记(新)——10蜂鸣器嘀嘀嘀
  • 微信小程序-数据模型与动态赋值
  • 【Redis】Linux下安装配置及通过C++访问Redis
  • Python 入门教程(4)数据类型 | 4.7、元组
  • Temu正在吸引越来越多的亚马逊卖家,这个市场Temu蝉联下载榜首
  • 设计原则模式概览
  • 高级主题:接口性能测试与压力测试
  • python绘制图像
  • 如何修复变砖的手机并恢复丢失的数据
  • 服务器使用了代理ip,遇到流量攻击,会对服务器有影响吗
  • 从存储到人工智能洞察: 利用 MinIO 和 Polars 简化数据管道
  • 只需要 1 分钟语音数据实现声音克隆
  • OpenEuler虚拟机安装保姆级教程 | 附可视化界面
  • 表格控件QTableWidget
  • LeetCode236题:二叉树的最近公共祖先
  • 虚谷中使用PL/SQL改变模式下所有表的大小写
  • 数据挖掘的基本步骤和流程解析:深入洞察与策略实施
  • BCJR算法——卷积码的最大后验译码
  • 系统架构设计师论文《论SOA在企业集成架构设计中的应用》精选试读
  • ceph rgw 桶分片之reshard
  • 开放原子开源基金会网站上的开源项目Opns存在缓冲区溢出缺陷
  • 未来前端发展方向:深度探索与技术前瞻
  • 前端工程规范-2:JS代码规范(Prettier + ESLint)
  • Tomcat为什么要打破双亲委派?怎么保证安全
  • 【C++篇】启航——初识C++(下篇)
  • Elasticsearch快速入门
  • uniapp微信小程序遮罩层u-popup禁止底层穿透
  • 【RocketMQ】秒杀设计与实现