LRU实现基于map和双向链表实现

简介: 如果我们遇到的缓存比较大时,此时又需要考虑怎样的缓存设计呢?此时由于缓存的消息或者信息比较大时,同时呈现成批量时,可以基于消息,考虑使用文件存储的方式进行,此时可以考虑NIO的处理方式,使用FileChannel和MappedByteBuffer或者堆外内存的方式,此时基于块状消息处理,使用缓冲区或者使用通道。此时的处理不经过用户态,因此性能上也是比较高的。当然基于LinkedHashMap也可以实现LRU缓存设计。LinkedHashMap本身就是基于HashMap+双向链表实现的。

前面我们已经看到了单链表的数据结构:数据域和节点域node。而双向链表则是:数据域和节点域(包含前驱节点和后继节点)。

单链表

微信图片_20221214031543.png

双向链表

微信图片_20221214031546.png

如果我们想完成一个简单的LRU的缓存,可以考虑基于双向链表和map实现。思路:

可以基于map的数据结构,value基于双向链表,也即有前驱节点和后继节点。而此时删除和添加操作时,其实实质是考虑双向链表的添加和删除操作。因此我们来看一下双向链表的添加和删除操作:

双向链表插入节点数据

微信图片_20221214031549.png


此时插入关系:

tail.next=node;
node.prev=tail;
node.next=null;
tail=node;

双向链表删除节点数据

微信图片_20221214031552.png


此时需要考虑前驱节点和后继节点

node.prev.next=node.next;
node.next.prev=node.prev;

因此基于此我们的LRU缓存的数据结构是:

publicLRUCache<K,V>{
privateintmaxCapacity; // 最大容量privateMap<K,Node<K,V>>map; // 缓存数据结构主体privateNode<K,V>prev,tail; // 存储数据双向链表,保证有序//节点域publicNode(Kkey, Vvalue) {
this.key=key;
this.value=value;
    }
}

此时考虑执行存放数据时,使用的是map的数据结构,因此需要考虑put的时候

1.首先考虑key是否存在,如果存在,则进行remove,然后执行放入操作put。2.否则,考虑容量是否充足,如果不够,则进行缓存过期操作。也即将队头的数据进行删除操作,然后执行存放数据操作。3.如果缓存容量够,则直接执行放入指针指向。

放入操作的实现

//放入缓存操作publicvoidput(Kkey, Vvalue) {
//判断map中是否包含key,如果包含,则需要执行更新操作,首先进行删除,然后执行更新if (map.containsKey(key)) {
Node<K, V>node=map.get(key);
node.value=value;
removeNode(node);
offerNode(node);
    } else {
//否者,首先考虑map容量是否充足,如果不充足,则考虑进行过期操作,// 也即进行删除操作,然后放入节点数据信息,然后放入到map中if (map.size() ==maxCapacity) {
map.remove(head.key);
removeNode(head);
        }
Node<K, V>node=newNode<>(key, value);
offerNode(node);
map.put(key, node);
    }
}

元素获取操作

//获取缓存数据publicVget(Kkey) {
Node<K, V>node=map.get(key);
//先判空,然后执行删除操作,执行放入操作if (node==null) returnnull;
removeNode(node);
offerNode(node);
returnnode.value;
}

而删除和添加元素的过程在上面的图中已经有提及,这里分析过程:

添加元素

1.如果当前节点为空,则直接返回,说明没有元素可添加2.如果当前节点头节点为空,说明插入的元素是第一个,此时head=tail=node3.否者,说明在插入当前节点元素的前就有元素了,因此此时考虑进行节点插入指向关系的考虑:因为有序,所以考虑尾插:tail.next=node;
node.prev=tail;
node.next=null;
tail=node;

元素删除操作

1.判断当前节点是否为空,如果为空,则直接返回,说明没有元素需要删除2.考虑当前节点的前驱节点不为空的情况,此时需要进行当前节点的前驱节点的后继节点指向当前节点的下一个节点,否则前驱节点为空,说明当前节点为头结点,此时将头结点指向下一个节点3.考虑当前节点的后继节点不为空的情况,此时需要将当前节点的下一节点的前驱节点指向当前节点的前驱节点,否者说明后继节点为空,则说明当前节点就是tail节点,此时需要将tail节点指向当前节点的前驱节点

但是此时我们看到的LRU程序是线程不安全的,同时如果想要在并发场景下使用,需要考虑线程安全的问题,因此此时我们考虑读写锁对其进行优化,同时考虑锁的粒度的细化。此时的读写锁放在map的get和put操作中。

但是如果想要性能也变得相对高一些,那还需要进行优化,此时我们考虑对锁进一步进行细化,同时利用cpu的多核处理器,也即采用分段锁的方式对锁进行细化。此时对对象进行锁的细化。

publicLruCacheV3(finalintmaxCapacity) {
intcores=Runtime.getRuntime().availableProcessors();
intconcurrency=cores<2?2 : cores;
cacheSegments=newLruCacheV2[concurrency];
intsegmentCapacity=maxCapacity/concurrency;
if (maxCapacity%concurrency==1) segmentCapacity++;
for (intindex=0; index<cacheSegments.length; index++) {
cacheSegments[index] =newLruCacheV2<>(segmentCapacity);
    }
}

此时对缓存对象进行细化。

如果我们遇到的缓存比较大时,此时又需要考虑怎样的缓存设计呢?

此时由于缓存的消息或者信息比较大时,同时呈现成批量时,可以基于消息,考虑使用文件存储的方式进行,此时可以考虑NIO的处理方式,使用FileChannel和MappedByteBuffer或者堆外内存的方式,此时基于块状消息处理,使用缓冲区或者使用通道。此时的处理不经过用户态,因此性能上也是比较高的。

当然基于LinkedHashMap也可以实现LRU缓存设计。LinkedHashMap本身就是基于HashMap+双向链表实现的。

代码实现参考波波老师讲的分布式系统设计。

目录
相关文章
|
1月前
|
数据安全/隐私保护
基于DAMON的LRU链表排序 【ChatGPT】
基于DAMON的LRU链表排序 【ChatGPT】
|
3月前
|
存储 C++
C++的list-map链表与映射表
```markdown C++ 中的`list`和`map`提供链表和映射表功能。`list`是双向链表,支持头尾插入删除(`push_front/push_back/pop_front/pop_back`),迭代器遍历及任意位置插入删除。`map`是键值对集合,自动按键排序,支持直接通过键来添加、修改和删除元素。两者均能使用范围for循环遍历,`map`的`count`函数用于统计键值出现次数。 ```
33 1
|
4月前
|
存储 C++
C++的list-map链表与映射表
这篇教程介绍了C++中`list`链表和`map`映射表的基本使用。`list`链表可通过`push_front()`、`push_back()`、`pop_front()`和`pop_back()`进行元素的添加和删除,使用迭代器遍历并支持在任意位置插入或删除元素。`map`是一个键值对的集合,元素自动按键值排序,可使用下标操作符或`insert()`函数插入元素,通过迭代器遍历并修改键值对,同时提供`count()`方法统计键值出现次数。教程中包含多个示例代码以帮助理解和学习。
42 2
|
5月前
|
存储 缓存 Java
java如何实现一个LRU(最近最少使用)缓存? 要求:设计一个LRU缓存,支持get和put操作。当缓存满时,需要淘汰最近最少使用的元素。要求使用双向链表+哈希表的数据结构来实现,并保证get和put操作的时间复杂度为O(1)。
java如何实现一个LRU(最近最少使用)缓存? 要求:设计一个LRU缓存,支持get和put操作。当缓存满时,需要淘汰最近最少使用的元素。要求使用双向链表+哈希表的数据结构来实现,并保证get和put操作的时间复杂度为O(1)。
59 1
|
5月前
|
存储 缓存 算法
【数据结构-链表 八】【链表模拟】模拟设计LRU缓存结构
【数据结构-链表 八】【链表模拟】模拟设计LRU缓存结构
56 0
|
存储 C++
C++的list-map链表与映射表
C++ list-map链表与映射表的简单使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值。
104 0
C++的list-map链表与映射表
|
存储 缓存
每日三题-合并K个升序链表、二叉树展开为链表、LRU缓存
每日三题 合并K个升序链表 二叉树展开为链表 LRU缓存
106 9
每日三题-合并K个升序链表、二叉树展开为链表、LRU缓存
|
存储 缓存 算法
【基础篇】4 # 链表(上):如何实现LRU缓存淘汰算法?
【基础篇】4 # 链表(上):如何实现LRU缓存淘汰算法?
164 0
【基础篇】4 # 链表(上):如何实现LRU缓存淘汰算法?
|
缓存 JavaScript
js:使用Map实现一个LRU缓存
js:使用Map实现一个LRU缓存
169 0
|
缓存 安全 Go
Golang:golang-lru一个基于双向链表实现的LRU缓存工具
Golang:golang-lru一个基于双向链表实现的LRU缓存工具
198 0