Java实现LRU(最近最少使用)算法

Java实现LRU(最近最少使用)算法

一、引言

LRU(Least Recently Used)算法,即最近最少使用算法,是一种常见的缓存淘汰策略。它基于局部性原理,即最近被访问的数据在未来一段时间内被访问的概率更高,而长时间未被访问的数据在未来一段时间内被访问的概率较低。在Java中实现LRU算法,可以有效地管理缓存数据,提高系统性能。

二、实现方式

在Java中实现LRU算法主要有两种方式:使用LinkedHashMap和自定义双向链表结合HashMap。

1、使用LinkedHashMap

Java的LinkedHashMap类提供了一个按访问顺序排序的功能,可以用来实现LRU缓存。通过设置accessOrder为true,LinkedHashMap会将最近访问的元素移动到链表的末尾,而最老的元素(链表的头部)则是最少被访问的元素。

1.1、步骤
  1. 创建一个LinkedHashMap实例,并设置一个初始容量和加载因子。
  2. 通过put和get方法操作缓存,LinkedHashMap会自动维护元素的访问顺序。
  3. 重写removeEldestEntry方法,当缓存容量超出限制时,自动移除最老的元素。

2、自定义双向链表结合HashMap

这种方式需要自己实现一个双向链表来维护缓存数据的顺序,同时使用HashMap来存储键和对应节点的引用,以实现快速查找。

2.1、步骤
  1. 定义一个双向链表节点类,包含键、值、前驱和后继指针。
  2. 创建一个缓存类,内部包含一个HashMap和一个双向链表。
  3. 在get和put方法中,根据访问情况更新节点在链表中的位置。
  4. 当缓存容量超出限制时,移除链表尾部的节点,即最老的元素。

三、代码实现

1、使用LinkedHashMap

java 复制代码
/**
 * @author ning0
 * @date 2024/9/23 12:03
 * @description Main
 **/
import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "one");
        cache.put(2, "two");
        cache.put(3, "three");
        cache.get(2); // 访问2,将其移到链表尾部
        cache.put(4, "four"); // 容量超出,移除最老的元素1
        System.out.println(cache.keySet()); // 输出:[3, 2, 4]
    }
}

2、自定义双向链表结合HashMap

java 复制代码
/**
 * @author ning0
 * @date 2024/9/23 12:03
 * @description Main
 **/

import java.util.HashMap;
import java.util.Map;

class LRUCache<K, V> {
    private final int capacity;
    private final Map<K, Node<K, V>> map;
    private final Node<K, V> head, tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new Node<>(null, null);
        tail = new Node<>(null, null);
        head.next = tail;
        tail.prev = head;
    }

    public V get(K key) {
        Node<K, V> node = map.get(key);
        if (node == null) return null;
        moveToHead(node);
        return node.value;
    }

    public void put(K key, V value) {
        Node<K, V> node = map.get(key);
        if (node == null) {
            node = new Node<>(key, value);
            map.put(key, node);
            addNode(node);
            if (map.size() > capacity) {
                map.remove(tail.prev.key);
                removeNode(tail.prev);
            }
        } else {
            node.value = value;
            moveToHead(node);
        }
    }

    private void addNode(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);
        addNode(node);
    }

    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;
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        Node<K, V> curr = head.next;
        while (curr != tail) {
            sb.append(curr.key).append(", ");
            curr = curr.next;
        }
        return sb.delete(sb.length() - 2, sb.length()).append("]").toString();
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "one");
        cache.put(2, "two");
        cache.put(3, "three");
        cache.get(2); // 访问2,将其移到链表头部
        cache.put(4, "four"); // 容量超出,移除最老的元素1
        System.out.println(cache); // 输出:[4, 2, 3]
    }
}

四、总结

LRU算法在缓存管理中扮演着重要角色,通过合理地淘汰最不常访问的数据,可以提高缓存的利用率和系统的性能。Java中实现LRU算法的两种方式各有优势,LinkedHashMap实现简单快捷,而自定义双向链表结合HashMap则提供了更高的灵活性。开发者可以根据实际需求选择合适的实现方式。


版权声明:本博客内容为原创,转载请保留原文链接及作者信息。

参考文章:

相关推荐
SL_staff9 小时前
私有化低代码:面向强监管行业的可控数字化实践
java·低代码
饮水桶_T9 小时前
JAVA IO流笔记
开发语言·数据结构·算法
三翼鸟数字化技术团队10 小时前
高性能存储引擎RocksDB:原理与Java生态实践
java
SL_staff10 小时前
JVS-APS有限产能建模的七个刚性支点:面向产线真实约束的技术实现解析
java·开源·全栈
特立独行的猫A10 小时前
少 3 个环节、少 1 次 ABI、少 3500 行代码:cj-tauri 与 Ionic/Capacitor 的鸿蒙桥接机制对比
java·后端·python
星夜离尘5610 小时前
MATLAB 离散数据分箱:半开区间、末端点与计数、加权和、平均
算法·matlab
Pniubi10 小时前
力扣76.最小覆盖子串
算法·leetcode·职场和发展
学长毕设10 小时前
基于SpringBoot的高校后勤报修管理系统-附源码
java·spring boot·后端
特立独行的猫A10 小时前
cj-tauri vs Rust/Tauri 同款播放器实测:编译 21 秒 vs 11 分钟,磁盘 12 MB vs 1.2 GB
java·后端·python
地平线开发者11 小时前
征程6|用 hrt\_model\_exec 看清 YOLOv5x 的输入、原始输出和后处理
人工智能·算法