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

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)); // 测试正常获取
    }
}

http://www.kler.cn/a/446843.html

相关文章:

  • mysql的事务控制和数据库的备份和恢复
  • javaFX.(蜜雪冰城点餐小程序)MySQL数据库
  • Vue3之路由(Router)介绍
  • VMware安装Ubuntu 16.04以及安装好后初步使用配置!
  • 全志H618 Android12修改doucmentsui鼠标单击图片、文件夹选中区域
  • wxWidgets使用wxStyledTextCtrl(Scintilla编辑器)的正确姿势
  • 扫雷游戏(基础版)
  • 前端开发性能监控中的数据采集与性能调优方法
  • 前端通过new Blob下载文档流(下载zip或excel)
  • uniapp小程序样式穿透
  • 深度学习实战车辆目标跟踪【bytetrack/deepsort】
  • notepad++快捷键-多行编辑中如何使所有行的光标都向后移动一个单词的长度(每行单词长度不一定一致)
  • cenos如何升级git到2以上版本
  • centos7下docker 容器实现redis主从同步
  • 智慧农业云平台与水肥一体化:道品科技引领农业现代化新潮流
  • 玩转树莓派Pico(19): 迷你气象站5——软件整合
  • 代码随想录第52天
  • 【数据分析】层次贝叶斯
  • 电子应用设计方案-64:智能窗帘系统方案设计
  • React与Vue的区别(相同点和不同点)
  • 一个签名笔迹量化分析专家辅助系统
  • 数据结构:B树与B+树
  • React 事件机制和原生 DOM 事件流有什么区别
  • 源码编译llama.cpp for android
  • linux下网络编程socketselectepoll的底层实现原理
  • js常用方法之: 预览大图(uniapp原生方法封装)