首页 > 其他分享 >LRU

LRU

时间:2024-02-29 22:44:40浏览次数:21  
标签:cache 链表 LRU key Entry entry 页面

什么是LRU

LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。

实现思路

开始时,内存中没有页面。
每次访问页面时,先检测内存中是否存在该页面,若不存在则将该页面加载到内存“末尾”,若存在则直接访问该页面,并将该页面移到内存“末尾”。
如果访问某个内存中不存在的页面时,内存已满,则将内存“开头”的页面移出,并将新的页面加载到内存“末尾”。
这样就可以始终保持着最近访问的页面在不经常访问的页面的后面了。

数据结构的选择

数组

页面命中时,需要将其移动到数组末尾。
页面命中失败时,需要删除数组头部元素,并将所有元素向前移动一步,然后在末尾添加元素。
这两个操作都涉及大量元素的移动,时间复杂度为O(n),效率较低。

链表

虽然不涉及到元素的移动,但是检查页面是否命中需要遍历链表,时间复杂度也为O(n)

双向链表 + 哈希表

双向链表维护最近使用的和很久没有使用页面的先后顺序,哈希表用于定位页面在双向链表中的位置,方便查找。

用双向链表是因为删除链表节点时,需要知道该节点的前驱节点。
双向链表两边特意加上虚拟头节点和虚拟伪节点,以使得链表两端元素的插入和删除操作与链表中间的元素保持一致。

基于Java中LinkedHashMap实现

在Java中,我们可以使用LinkedHashMap来实现LRU算法。LinkedHashMap是Java集合框架中的一个具体实现,它继承自HashMap,但是又以双向链表的形式维护元素的顺序。我们可以设置LinkedHashMap的访问顺序模式(accessOrder)为true,这样每次访问一个元素时,它会被放到最后。当缓存空间不足时,我们只需要删除链表头部的元素即可。

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, "A");
        cache.put(2, "B");
        cache.put(3, "C");
        System.out.println(cache); // 输出:{1=A, 2=B, 3=C}        
        cache.get(2); // 访问key为2的元素
        System.out.println(cache); // 输出:{1=A, 3=C, 2=B}       
        cache.put(4, "D"); // 添加新的元素,触发删除最久未被访问的元素
        System.out.println(cache); // 输出:{3=C, 2=B, 4=D}
    }
}

我们创建了一个LRUCache类,继承自LinkedHashMap。在构造函数中,我们指定了缓存的容量。removeEldestEntry方法被重写,用于判断是否需要删除最久未被访问的元素。如果缓存超过容量,我们返回true,表示需要删除最老的元素。在main方法中,我们创建了一个容量为3的LRU缓存,进行了一些操作来展示LRU算法的工作机制。

基于自定义数据结构实现(HashMap+双向链表)

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

/**
 * @Desc 采用LRU置换算法的缓存
 */
public class LRUCache<K, V> {

    // 静态内部类,双向链表中的节点类,key理解为页面号,val理解为页面内容
    static class Entry<K, V> {
        public Entry<K, V> prev;
        public Entry<K, V> next;
        public K key;
        public V val;
        public Entry() {}
        public Entry(K key, V val) { this.key = key; this.val = val; }
    }

    private Entry<K, V> head, tail; // 虚拟头节点和虚拟尾节点
    private final int capacity;     // 缓存容量
    private int size;               // 缓存占用量
    Map<K, Entry<K, V>> cache;      // 哈希表,记录双向列表节点的地址值

    public LRUCache(int capacity) {
        this.capacity = capacity;
        initCache();
    }

    // 初始化LRU缓存
    private void initCache() {
        head = new Entry<>();
        tail = new Entry<>();
        head.next = tail;
        tail.prev = head;
        size = 0;
        cache = new HashMap<>(this.capacity);
    }

    private V get(K key) {
        Entry<K, V> entry = cache.get(key);
        if(entry != null) {
            moveToTail(entry);
            return entry.val;
        } else {
            return null;
        }
    }

    private void put(K key, V val) {
        Entry<K, V> entry = cache.get(key);
        if(entry != null) {
            // 缓存命中
            entry.val = val;
            moveToTail(entry);
        } else {
            // 缓存未命中
            if(size == capacity) {
                // 缓存已满,删除链表头部节点
                Entry<K, V> h = head.next;
                deleteEntry(h);
                cache.remove(h.key);
                size--;
            }
            // 添加新页面到链表尾部
            Entry<K, V> newEntry = new Entry<>(key, val);
            addToTail(newEntry);
            cache.put(key, newEntry);
            size++;
        }
    }

    private void moveToTail(Entry<K, V> entry) {
        deleteEntry(entry);
        addToTail(entry);
    }

    private void addToTail(Entry<K, V> entry) {
        if(entry != null) {
            entry.next = tail;
            entry.prev = tail.prev;
            tail.prev.next = entry;
            tail.prev = entry;
        }
    }

    private void deleteEntry(Entry<K, V> entry) {
        if(entry != null) {
            entry.prev.next = entry.next;
            entry.next.prev = entry.prev;
        }
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(2);
        cache.put(1,"可口可乐");
        cache.put(2,"雪碧");
        System.out.println("页面1的内容:" + cache.get(1));
        cache.put(3,"果粒橙"); // 此时缓存已满,且页面2最久未被使用(因为cache.get(1)访问了页面1),页面2被置换成页面3
        System.out.println("页面2的内容:" + cache.get(2)); // 页面2已被换出,访问不到
    }
}

运行结果:

页面1的内容:可口可乐
页面2的内容:null

 

标签:cache,链表,LRU,key,Entry,entry,页面
From: https://www.cnblogs.com/zhengbiyu/p/18045751

相关文章

  • 开年喜报!Walrus成功入选CNCF云原生全景图
    近日,数澈软件Seal(以下简称“Seal”)旗下开源应用管理平台Walrus成功入选云原生计算基金会全景图(CNCFLandscape)并收录至“AppDefinitionandDevelopment-ApplicationDefinition&ImageBuild”板块,该板块包含了Helm、Backstage、Dapr等知名开源项目。 图片截自:htt......
  • 力扣链表 哈希表 之 146. LRU 缓存
    请你设计并实现一个满足 LRU(最近最少使用)缓存约束的数据结构。实现LRUCache类:LRUCache(intcapacity)以正整数作为容量 capacity初始化LRU缓存intget(intkey)如果关键字key存在于缓存中,则返回关键字的值,否则返回-1。voidput(intkey,intvalue) ......
  • ILRuntime是如何实现热更新的
    一、ILRuntime的基本原理ILRuntime的基本原理是将C#代码编译成IL代码,然后在运行时通过IL解释器将其转换成机器码执行。这种方式与传统的AOT(AheadofTime)编译方式不同,传统的AOT编译方式是在编译时将C#代码编译成机器码,然后在运行时直接执行机器码。由于ILRuntime是在运行时解释......
  • ILRuntime编码中如何注意性能问题
    一、避免频繁的反射操作在使用ILRuntime时,我们需要频繁地进行反射操作,例如获取类型、获取方法、获取属性等等。反射操作是非常耗费性能的,所以我们需要尽可能地避免频繁的反射操作。例如,我们需要获取一个类型的所有属性,我们可以使用以下代码:PropertyInfo[]properties=typeof......
  • Walrus 实用教程|Walrus + Gitlab,打通CI/CD 自动化交付!
    Walrusfile是Walrus0.5版本推出的新功能,用户可以通过一个非常简洁的YAML描述应用或基础设施资源的部署配置,然后通过WalrusCLI执行walrusapply或在WalrusUI上进行import,将Walrusfile提交给Walrusserver,由Walrusserver完成对应用或基础设施资源的部署/配置/......
  • Walrus 0.5发布:重构交互流程,打造开箱即用的部署体验
    开源应用管理平台Walrus0.5已于近日正式发布! Walrus0.4引入了全新应用模型,极大程度减少了重复的配置工作,并为研发团队屏蔽了云原生及基础设施的复杂度。Walrus0.5在这一基础上,通过重构交互流程、增强抽象能力,打造开箱即用的产品体验,进一步以平台工程的方式优化应用部署......
  • ILRuntime是如何与Unity互相调用的
    ILRuntime是一个跨平台CLR实现,它可以在多个平台上运行C#代码,包括Android、iOS、Windows、Linux等等。ILRuntime的实现方式是将C#代码编译成IL代码,然后在运行时通过JIT或AOT的方式将IL代码转换为机器代码,从而实现跨平台的效果。ILRuntime的主要功能包括热更新、动态加载、代码加密......
  • 【算法】【线性表】【链表】LRU 缓存
    1 题目请你设计并实现一个满足  LRU(最近最少使用)缓存 约束的数据结构。实现 LRUCache 类:LRUCache(intcapacity) 以 正整数 作为容量 capacity 初始化LRU缓存intget(intkey) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。voidput......
  • [ABC273D] LRUD Instructions 题解
    [ABC273D]LRUDInstructions题解很好的一道大模拟,使我爆\(0\)。思路解析大模拟,我们只需要用一个\(x,y\)表示我们当前的位置,而对于每一个移动,我们就判断在当前移动方向上离当前点最近的点,若该点在当前行进路线上,则把当前位置设为该点前面的一个。其中判断在当前移动方向上......
  • 基于DAMON的LRU链表排序 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/admin-guide/mm/damon/lru_sort.htmlDAMON-basedLRU-listsSortingDAMON-basedLRU-listsSorting(DAMON_LRU_SORT)是一个静态内核模块,旨在用于基于主动和轻量级数据访问模式的(去)优先级排序,以使LRU列表成为更可信赖的数据访问模式源......