news 2026/7/10 12:03:25

实现一个LRU缓存淘汰策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
实现一个LRU缓存淘汰策略

LRU(Least Recently Used,最近最少使用)缓存淘汰策略的核心是:当缓存容量满时,淘汰最久未被使用的元素。在 Java 中,最优实现方式是结合HashMap(快速查找)和LinkedList/LinkedHashMap(维护访问顺序),其中LinkedHashMap是官方推荐的简化实现方式,而手动实现双链表 + 哈希表能更深入理解底层原理。

一、核心原理

  1. 数据结构选择
    • 哈希表(HashMap):存储「键 - 节点」映射,实现 O (1) 时间复杂度的查找 / 更新。
    • 双向链表:维护节点的访问顺序,头部为最近使用的节点,尾部为最久未使用的节点
  1. 核心操作
    • get(key):若键存在,将节点移到链表头部(标记为最近使用),返回值;若不存在,返回 -1。
    • put(key, value)
      • 若键存在,更新值并将节点移到链表头部;
      • 若键不存在,创建新节点并插入链表头部,同时存入哈希表;
      • 若缓存容量超限,删除链表尾部节点(最久未使用),并从哈希表中移除对应键。

二、手动实现(链表 + HashMap)

手动实现能清晰体现 LRU 的核心逻辑,也是面试高频考点:

import java.util.HashMap; import java.util.LinkedList; import java.util.Map; public class LRUCache<K, V> { private final int capacity; // 缓存容量 private final Map<K, V> cache; // 缓存键值对 private final LinkedList<K> keyList; // 维护key的访问顺序(尾部=最近使用,头部=最久未使用) // 初始化:校验容量合法性 public LRUCache(int capacity) { if (capacity <= 0) { throw new IllegalArgumentException("缓存容量必须大于0"); } this.capacity = capacity; this.cache = new HashMap<>(capacity); this.keyList = new LinkedList<>(); } // put操作:存入/更新key-value,保证O(1)核心逻辑(仅修复逻辑,未优化性能) public synchronized void put(K key, V value) { // 1. 若key已存在:先移除keyList中的旧位置,再删除缓存旧值 if (cache.containsKey(key)) { keyList.remove(key); // 注意:此处仍为O(n),后续优化会解决 cache.remove(key); } // 2. 若缓存已满:删除最久未使用的key(链表头部) if (cache.size() >= capacity) { K oldestKey = keyList.removeFirst(); cache.remove(oldestKey); } // 3. 存入新值,并将key加入链表尾部(标记为最近使用) cache.put(key, value); keyList.addLast(key); } // get操作:获取value并更新访问顺序 public synchronized V get(K key) { // 1. 缓存中不存在key,返回null if (!cache.containsKey(key)) { return null; } // 2. 存在key:更新访问顺序(移除旧位置,加入尾部) keyList.remove(key); // 此处仍为O(n),后续优化会解决 keyList.addLast(key); return cache.get(key); } // 辅助方法:打印缓存和keyList(用于测试) public void printCache() { System.out.println("缓存内容:" + cache); System.out.println("key访问顺序:" + keyList); } // 测试示例 public static void main(String[] args) { LRUCache<Integer, String> lru = new LRUCache<>(2); lru.put(1, "A"); lru.put(2, "B"); lru.printCache(); // 缓存:{1=A, 2=B},keyList:[1,2] lru.get(1); // 访问1,更新顺序 lru.printCache(); // 缓存:{1=A, 2=B},keyList:[2,1] lru.put(3, "C"); // 容量满,删除最久未使用的2 lru.printCache(); // 缓存:{1=A, 3=C},keyList:[1,3] lru.put(1, "AA"); // 更新1的值,更新顺序 lru.printCache(); // 缓存:{1=AA, 3=C},keyList:[3,1] } }

三、性能优化:解决 LinkedList.remove (key) 的 O (n) 问题

要让get/put操作真正达到 O (1) 时间复杂度,需替换 LinkedList 为「双向链表 + 哈希表记录节点」(即我之前提到的手动实现双链表方案)。以下是优化后的最终版本:

import java.util.HashMap; import java.util.Map; public class LRUCache<K, V> { // 双向链表节点:存储key、value,以及前驱/后继节点 private static class Node<K, V> { K key; V value; Node<K, V> prev; Node<K, V> next; public Node(K key, V value) { this.key = key; this.value = value; } } private final int capacity; // 缓存容量 private final Map<K, Node<K, V>> nodeMap; // key -> 节点(O(1)查找) private final Node<K, V> head; // 虚拟头节点(最近使用) private final Node<K, V> tail; // 虚拟尾节点(最久未使用) // 初始化:虚拟头尾节点相连,避免空指针 public LRUCache(int capacity) { if (capacity <= 0) { throw new IllegalArgumentException("缓存容量必须大于0"); } this.capacity = capacity; this.nodeMap = new HashMap<>(capacity); this.head = new Node<>(null, null); this.tail = new Node<>(null, null); head.next = tail; tail.prev = head; } // put操作:存入/更新key-value(O(1)时间复杂度) public synchronized void put(K key, V value) { Node<K, V> node = nodeMap.get(key); if (node != null) { // 1. key已存在:更新value,并移到头部(最近使用) node.value = value; moveToHead(node); return; } // 2. key不存在:创建新节点 Node<K, V> newNode = new Node<>(key, value); nodeMap.put(key, newNode); addToHead(newNode); // 3. 缓存已满:删除尾节点(最久未使用) if (nodeMap.size() > capacity) { Node<K, V> tailNode = removeTail(); nodeMap.remove(tailNode.key); } } // get操作:获取value并更新访问顺序(O(1)时间复杂度) public synchronized V get(K key) { Node<K, V> node = nodeMap.get(key); if (node == null) { return null; } // 移到头部,标记为最近使用 moveToHead(node); return node.value; } // ========== 双向链表辅助方法(均为O(1)操作) ========== // 将节点添加到虚拟头节点之后(最近使用位置) private void addToHead(Node<K, V> node) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } // 移除指定节点 private void removeNode(Node<K, V> node) { node.prev.next = node.next; node.next.prev = node.prev; } // 将节点移到头部(先移除,再添加) private void moveToHead(Node<K, V> node) { removeNode(node); addToHead(node); } // 移除尾节点(最久未使用) private Node<K, V> removeTail() { Node<K, V> tailNode = tail.prev; removeNode(tailNode); return tailNode; } // 辅助方法:打印缓存(用于测试) public void printCache() { StringBuilder sb = new StringBuilder(); Node<K, V> cur = head.next; while (cur != tail) { sb.append(cur.key).append("=").append(cur.value).append(", "); cur = cur.next; } System.out.println("缓存内容(最近使用→最久未使用):" + sb); } // 测试示例 public static void main(String[] args) { LRUCache<Integer, String> lru = new LRUCache<>(2); lru.put(1, "A"); lru.put(2, "B"); lru.printCache(); // 1=A, 2=B lru.get(1); lru.printCache(); // 2=B, 1=A(1移到最近使用) lru.put(3, "C"); lru.printCache(); // 1=A, 3=C(删除最久未使用的2) lru.put(1, "AA"); lru.printCache(); // 3=C, 1=AA(更新1并移到最近使用) } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/10 1:29:11

kotaemon本地化隐私保护方案详解

Kotaemon本地化隐私保护方案详解 在AI技术加速渗透企业核心业务的当下&#xff0c;一个尖锐的问题摆在开发者面前&#xff1a;如何在享受大模型智能红利的同时&#xff0c;守住数据安全的生命线&#xff1f;尤其对于金融、医疗等敏感行业&#xff0c;哪怕是最细微的数据外泄风险…

作者头像 李华
网站建设 2026/7/10 10:07:50

Python爬虫实战:基于异步技术的大宗商品期货交易数据爬取与趋势分析

引言:期货数据爬虫的重要性与挑战 在当今数字化金融时代,期货市场交易数据已成为投资者、分析师和研究人员进行大宗商品价格趋势分析的关键资源。期货数据不仅反映了市场供需关系,还包含了宏观经济、政策变化和全球事件的影响。然而,获取高质量、实时的期货交易数据面临着…

作者头像 李华
网站建设 2026/7/10 7:36:08

46、Linux使用指南:从基础到高级的全面攻略

Linux使用指南:从基础到高级的全面攻略 一、Linux基础概念 1.1 “Free”的含义 在特定语境中,“free”指的是自由或自主,而非价格层面的免费。这种区别在相关介绍中会有详细解释。 1.2 Unix的起源 “Unix”最初写作“Unics”,代表“Uniplex Information and Computing…

作者头像 李华
网站建设 2026/7/9 20:44:59

LobeChat能否用于生成广告语?品牌传播创意工厂

LobeChat能否用于生成广告语&#xff1f;品牌传播创意工厂 在品牌营销的战场上&#xff0c;一句精准有力的广告语&#xff0c;往往能撬动千万级的市场认知。然而&#xff0c;传统创意流程依赖少数“天才文案”&#xff0c;不仅成本高昂&#xff0c;且难以规模化响应快速变化的消…

作者头像 李华
网站建设 2026/7/10 10:10:29

Windows下TensorFlow 2.5 GPU环境配置指南

Windows下TensorFlow 2.5 GPU环境配置实战指南 在深度学习项目中&#xff0c;训练一个大型模型动辄需要数小时甚至几天。如果你还在用CPU跑TensorFlow&#xff0c;那可能连“调参侠”的门槛都还没迈进去——等你调完一组超参数&#xff0c;别人已经跑完三轮实验了。 真正高效…

作者头像 李华
网站建设 2026/7/10 10:09:56

一键 i18n 国际化神库!适配 Vue、React!

一键 i18n 国际化神库&#xff01;适配 Vue、React&#xff01; 作为一名前端开发&#xff0c;给项目做多语言早已是家常便饭。 可每次面对成百上千条文案&#xff0c;“一行行手动翻译 维护 N 个 JSON 文件”仍然是让人头秃的体力活。 今天就把我实测有效的 3 款零入侵、一…

作者头像 李华