java 基于冷热数据分离的思想设计LRU链表

java 基于冷热数据分离的思想设计LRU链表

1. LRUCache 伪代码
import java.util.HashMap;
import java.util.Map;

public class LRUCache {
    private final int capacity; // 缓存的最大容量
    private final Map<Integer, Node> map; // 用于快速查找节点的哈希表
    private final Node headHot, tailHot; // 热数据链表的头节点和尾节点
    private final Node headCold, tailCold; // 冷数据链表的头节点和尾节点
    private int hotSize, coldSize; // 热数据和冷数据的数量

    public LRUCache(int capacity) {
        this.capacity = capacity; // 初始化缓存容量
        this.map = new HashMap<>(); // 初始化哈希表
        this.headHot = new Node(0, 0); // 初始化热数据链表的头节点
        this.tailHot = new Node(0, 0); // 初始化热数据链表的尾节点
        this.headCold = new Node(0, 0); // 初始化冷数据链表的头节点
        this.tailCold = new Node(0, 0); // 初始化冷数据链表的尾节点
        headHot.next = tailHot; // 将头节点和尾节点连接起来
        tailHot.prev = headHot;
        headCold.next = tailCold; // 将头节点和尾节点连接起来
        tailCold.prev = headCold;
        this.hotSize = 0; // 初始化热数据数量为0
        this.coldSize = 0; // 初始化冷数据数量为0
    }

    public int get(int key) {
        Node node = map.get(key); // 从哈希表中查找节点
        if (node == null) return -1; // 如果节点不存在,返回-1
        if (node.isHot) {
            // 如果节点是热数据,将其移动到热数据链表头部
            removeNode(node);
            addToHeadHot(node);
        } else {
            // 如果节点是冷数据,将其移动到冷数据链表头部
            removeNode(node);
            addToHeadCold(node);
            // 如果冷数据链表的大小超过容量的一半,移除最久未使用的冷数据
            if (coldSize > capacity / 2) {
                Node lruCold = tailCold.prev;
                removeNode(lruCold);
                map.remove(lruCold.key);
                coldSize--;
                // 如果热数据链表的大小小于容量的一半,将最久未使用的冷数据提升为热数据
                if (hotSize < capacity / 2) {
                    addToHeadHot(lruCold);
                    lruCold.isHot = true;
                    hotSize++;
                }
            }
        }
        return node.value; // 返回节点的值
    }

    public void put(int key, int value) {
        if (capacity == 0) return; // 如果缓存容量为0,直接返回
        Node node = map.get(key); // 从哈希表中查找节点
        if (node != null) {
            // 如果节点存在,更新其值
            node.value = value;
            if (node.isHot) {
                // 如果节点是热数据,将其移动到热数据链表头部
                removeNode(node);
                addToHeadHot(node);
            } else {
                // 如果节点是冷数据,将其移动到冷数据链表头部
                removeNode(node);
                addToHeadCold(node);
                // 如果冷数据链表的大小超过容量的一半,移除最久未使用的冷数据
                if (coldSize > capacity / 2) {
                    Node lruCold = tailCold.prev;
                    removeNode(lruCold);
                    map.remove(lruCold.key);
                    coldSize--;
                    // 如果热数据链表的大小小于容量的一半,将最久未使用的冷数据提升为热数据
                    if (hotSize < capacity / 2) {
                        addToHeadHot(lruCold);
                        lruCold.isHot = true;
                        hotSize++;
                    }
                }
            }
        } else {
            // 如果节点不存在,创建新节点
            if (hotSize + coldSize >= capacity) {
                // 如果缓存已满,移除最久未使用的节点
                if (coldSize > capacity / 2) {
                    Node lruCold = tailCold.prev;
                    removeNode(lruCold);
                    map.remove(lruCold.key);
                    coldSize--;
                } else {
                    Node lruHot = tailHot.prev;
                    removeNode(lruHot);
                    map.remove(lruHot.key);
                    hotSize--;
                }
            }
            Node newNode = new Node(key, value); // 创建新节点
            addToHeadHot(newNode); // 将新节点插入到热数据链表头部
            map.put(key, newNode); // 将新节点添加到哈希表中
            hotSize++; // 更新热数据数量
        }
    }

    private void addToHeadHot(Node node) {
        node.isHot = true; // 设置节点为热数据
        node.prev = headHot; // 将节点插入到热数据链表头部
        node.next = headHot.next;
        headHot.next.prev = node;
        headHot.next = node;
        hotSize++; // 更新热数据数量
    }

    private void addToHeadCold(Node node) {
        node.isHot = false; // 设置节点为冷数据
        node.prev = headCold; // 将节点插入到冷数据链表头部
        node.next = headCold.next;
        headCold.next.prev = node;
        headCold.next = node;
        coldSize++; // 更新冷数据数量
    }

    private void removeNode(Node node) {
        if (node.isHot) {
            hotSize--; // 如果节点是热数据,更新热数据数量
        } else {
            coldSize--; // 如果节点是冷数据,更新冷数据数量
        }
        node.prev.next = node.next; // 从链表中移除节点
        node.next.prev = node.prev;
    }

    private static class Node {
        int key; // 节点的键
        int value; // 节点的值
        boolean isHot; // 节点是否为热数据
        Node prev; // 前驱节点
        Node next; // 后继节点

        Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
}

说明
数据结构:
使用双向链表来存储热数据和冷数据。
使用哈希表来快速查找节点。
操作:
get(int key): 如果节点存在且是热数据,则将其移动到热数据链表头部;如果是冷数据,则将其移动到冷数据链表头部,并根据冷数据链表的大小决定是否将其提升为热数据。
put(int key, int value): 如果节点存在,则更新其值并根据其状态移动到相应链表的头部;如果节点不存在,则创建新节点并插入到热数据链表头部,同时根据缓存容量决定是否需要移除最久未使用的节点。
冷热数据分离:
通过维护两个链表分别存储热数据和冷数据,可以有效地分离冷热数据。
当冷数据链表的大小超过缓存容量的一半时,会将最久未使用的冷数据移除,并根据热数据链表的大小决定是否将其提升为热数据。
这种设计可以提高缓存的命中率和访问速度,特别是在访问模式存在明显冷热数据分离的情况下。

2. LRU 的单元测试
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class LRUCacheTest {
    
    private LRUCache cache;

    @BeforeEach
    public void setUp() {
        cache = new LRUCache(3); // 初始化缓存容量为3
    }

    @Test
    public void testPutAndGet() {
        cache.put(1, 1);
        cache.put(2, 2);
        cache.put(3, 3);

        assertEquals(1, cache.get(1)); // 测试正常获取
        assertEquals(2, cache.get(2)); // 测试正常获取
        assertEquals(3, cache.get(3)); // 测试正常获取
    }

    @Test
    public void testEviction() {
        cache.put(1, 1);
        cache.put(2, 2);
        cache.put(3, 3);
        cache.put(4, 4); // 触发淘汰

        assertEquals(-1, cache.get(1)); // 测试淘汰最久未使用的元素
        assertEquals(2, cache.get(2)); // 测试正常获取
        assertEquals(3, cache.get(3)); // 测试正常获取
        assertEquals(4, cache.get(4)); // 测试正常获取
    }

    @Test
    public void testUpdate() {
        cache.put(1, 1);
        cache.put(2, 2);
        cache.put(1, 10); // 更新值

        assertEquals(10, cache.get(1)); // 测试更新后的值
        assertEquals(2, cache.get(2)); // 测试未更新的值
    }

    @Test
    public void testGetUpdatesOrder() {
        cache.put(1, 1);
        cache.put(2, 2);
        cache.put(3, 3);
        cache.get(1); // 访问1,变为最近使用

        cache.put(4, 4); // 触发淘汰

        assertEquals(1, cache.get(1)); // 测试正常获取
        assertEquals(-1, cache.get(2)); // 测试淘汰最久未使用的元素
        assertEquals(3, cache.get(3)); // 测试正常获取
        assertEquals(4, cache.get(4)); // 测试正常获取
    }

    @Test
    public void testCapacityZero() {
        LRUCache zeroCapacityCache = new LRUCache(0);
        zeroCapacityCache.put(1, 1);
        assertEquals(-1, zeroCapacityCache.get(1)); // 测试容量为0时无法存储元素
    }

    @Test
    public void testColdToHot() {
        cache.put(1, 1);
        cache.put(2, 2);
        cache.put(3, 3);
        cache.get(1); // 访问1,变为最近使用
        cache.get(2); // 访问2,变为最近使用
        cache.get(3); // 访问3,变为最近使用

        cache.put(4, 4); // 触发淘汰
        cache.put(5, 5); // 触发淘汰

        assertEquals(1, cache.get(1)); // 测试正常获取
        assertEquals(2, cache.get(2)); // 测试正常获取
        assertEquals(3, cache.get(3)); // 测试正常获取
        assertEquals(-1, cache.get(4)); // 测试淘汰最久未使用的元素
        assertEquals(5, cache.get(5)); // 测试正常获取
    }

    @Test
    public void testHotToCold() {
        cache.put(1, 1);
        cache.put(2, 2);
        cache.put(3, 3);
        cache.get(1); // 访问1,变为最近使用
        cache.get(2); // 访问2,变为最近使用
        cache.get(3); // 访问3,变为最近使用

        cache.put(4, 4); // 触发淘汰
        cache.get(1); // 访问1,变为最近使用
        cache.get(2); // 访问2,变为最近使用

        cache.put(5, 5); // 触发淘汰

        assertEquals(1, cache.get(1)); // 测试正常获取
        assertEquals(2, cache.get(2)); // 测试正常获取
        assertEquals(-1, cache.get(3)); // 测试淘汰最久未使用的元素
        assertEquals(4, cache.get(4)); // 测试正常获取
        assertEquals(5, cache.get(5)); // 测试正常获取
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/940496.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数字经济下的 AR 眼镜

目录 1. &#x1f4c2; AR 眼镜发展历史 1.1 AR 眼镜相关概念 1.2 市面主流 XR 眼镜 1.3 AR 眼镜大事记 1.4 国内外 XR 眼镜 1.5 国内 AR 眼镜四小龙 2. &#x1f531; 关键技术 2.1 AR 眼镜近眼显示原理 2.2 AR 眼镜关键技术 2.3 AR 眼镜技术难点 3. &#x1f4a…

maven-resources-production:ratel-fast: java.lang.IndexOutOfBoundsException

Maven生产环境中遇到java.lang.IndexOutOfBoundsException的问题&#xff0c;尝试了重启电脑、重启IDEA等常规方法无效&#xff0c;最终通过直接重建工程解决了问题。 Rebuild Project 再启动OK

TDesign:NavBar 导航栏

NavBar 导航栏 左图&#xff0c;右标 appBar: TDNavBar(padding: EdgeInsets.only(left: 0,right: 30.w), // 重写左右内边距centerTitle:false, // 不显示标题height: 45, // 高度titleWidget: TDImage( // 左图assetUrl: assets/img/logo.png,width: 147.w,height: 41.w,),ba…

【计算机网络】lab2 Ethernet(链路层Ethernet frame结构细节)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;计算机网络_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2.…

ansible剧本快速上手

playbook剧本介绍 是什么&#xff1a;能户长期保存&#xff0c;且能实现批量配置、部署…的文件格式&#xff1a;yaml格式。用 空格 冒号 头号 句号语法检测&#xff1a;ansible-playbook --syntax-check install-zabbix.yaml或则 -C检测取消默认任务&#xff1a;gather_facts…

【LeetCode每日一题】——434.字符串中的单词数

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【时空频度】八【代码实现】九【提交结果】 一【题目类别】 字符串 二【题目难度】 简单 三【题目编号】 434.字符串中的单词数 四【题目描述】 统计字符串中的单词个…

C++ OpenGL学习笔记(1、Hello World空窗口程序)

终于抽出时间系统学习OpenGL 教程&#xff0c;同时也一步一步记录怎样利用openGL进行加速计算。 目录 1、环境准备1.1、库的下载1.2、库的选择及安装 2、OpenGL第一个项目&#xff0c;Hello World!2.1、新建hello world控制台项目2.2、配置openGL环境2.2.1 包含目录配置2.2.2 …

MySQL复制问题和解决

目录 环境介绍 一&#xff0c;主库执行delete&#xff0c;从库没有该数据 模拟故障 修复故障 二&#xff0c;主库执行insert&#xff0c;从库已存在该数据 模拟故障 故障恢复 三&#xff0c;主库执行update&#xff0c;从库没有该数据 模拟故障 故障恢复 四&#xf…

AWTK 在树莓派 pico 上的移植笔记

1. 配置文件 (awtk_config.h) pico 和 stm32f103 的配置差不多&#xff0c;虽然 pico 的内存要大不少&#xff0c;但是也不足提供一个完整的 FrameBuffer&#xff0c;所以只能使用片段 LCD。 我们在 awtk-stm32f103 的配置 基础稍作修改即可。 /* 使用片段 LCD */#define FRA…

构建MacOS应用小白教程(打包 签名 公证 上架)

打包 在package.json中&#xff0c;dependencies会被打进 Electron 应用的包里&#xff0c;而devDependencies则不会&#xff0c;所以必要的依赖需要放到dependencies中。files中定义自己需要被打进 Electron 包里的文件。以下是一个完整的 mac electron-builder的配置文件。 …

flink sink doris

接上文&#xff1a;一文说清flink从编码到部署上线 网上关于flink sink drois的例子较多&#xff0c;大部分不太全面&#xff0c;故本文详细说明&#xff0c;且提供完整代码。 1.添加依赖 <!--doris cdc--><!-- 参考&#xff1a;"https://doris.apache.org/zh-C…

GhostRace: Exploiting and Mitigating Speculative Race Conditions-记录

文章目录 论文背景Spectre-PHT&#xff08;Transient Execution &#xff09;Concurrency BugsSRC/SCUAF和实验条件 流程Creating an Unbounded UAF WindowCrafting Speculative Race ConditionsExploiting Speculative Race Conditions poc修复flush and reload 论文 https:/…

【STM32 Modbus编程】-作为主设备写入多个线圈和寄存器

作为主设备写入多个线圈和寄存器 文章目录 作为主设备写入多个线圈和寄存器1、硬件准备与连接1.1 RS485模块介绍1.2 硬件配置与接线1.3 软件准备2、写入多个线圈2.1 数据格式2.2 发送数据2.3 结果3、写入多个寄存器3.1 数据格式3.2 发送数据3.3 结果本文将实现STM32作为ModBus主…

国标GB28181协议平台Liveweb:搭建建筑工地无线视频联网监控系统方案

随着科技高速发展&#xff0c;视频信号经过数字压缩&#xff0c;通过互联网宽带或者移动4G网络传递&#xff0c;可实现远程视频监控功能。将这一功能运用于施工现场安全管理&#xff0c;势必会大大提高管理效率&#xff0c;提升监管层次。而这些&#xff0c;通过Liveweb监控系统…

AS-REP Roasting离线爆破攻击

针对一个域内用户&#xff0c;其账户选项有个设置叫作 “不要求 kerberos 预身份验证”&#xff0c;它默认是关闭的。 当 “不要求 kerberos 预身份验证” 选项被勾选&#xff0c;会出现以下效果&#xff1a; as-req 报文中不需要添加用户 hash 加密的时间戳&#xff0c;自动返…

python中的局部变量、全局变量问题的思考(对比于c语言)

今天在运行python时遇到了局部变量和全局变量的问题&#xff0c;令我很迷惑。 首先&#xff0c;我在学习python之前先学习了c语言&#xff0c;所以c语言的一些东西影响了我对这个问题的思考。 在c语言中 局部变量和全局变量的区别就在于作用域的范围大小。在c语言中&#xf…

进网许可认证、交换路由设备检测项目更新25年1月起

实施时间 2025年1月1日起实施 涉及设备范围 核心路由器、边缘路由器、以太网交换机、三层交换机、宽带网络接入服务器&#xff08;BNAS&#xff09; 新增检测依据 GBT41266-2022网络关键设备安全检测方法交换机设备 GBT41267-2022网络关键设备安全技术要求交换机设备 GB/…

文件,IO流

目录 一 java 1. IO流 1&#xff09;输入输出&#xff08;以程序的视角判断 &#xff09; 1.1 IO流的分类 1&#xff09;字符流效率高于字节流 1.2 流和文件的关系 2. inputstream--字节输入流 2.1 fileinputstream 2.1.1常用方法&#xff1a; 1&#xff09;单个字符…

pymssql-2.1.4.dev5-cp37-cp37m-win_amd64.whl 安装

pip install pymssql 安装pymssql出现下面的问题 error: Microsoft Visual C 14.0 is required. Get it with “Microsoft Visual C Build Tools”: http://landinghub.visualstudio.com/visual-cpp-build-tools 因为要使用python连接sqlserver数据库&#xff0c;需要pymssq…

vue中验证码的实现方式

在写登录页的时候有的系统会让你也进行一下验证码绘制&#xff0c;那么验证码如何实现的呢&#xff1f;我在写登录页的时候通过将登录框&#xff0c;验证码分开页面来写&#xff0c;最后将它们变成标签来导入到我的样式页面中&#xff0c;这样写不仅方便&#xff0c;更容易修改…