在操作系统中,段式存储、页式存储与段页式存储是文件存储的重要方式。其中,在页式存储中,CPU为了快速获取页的内容,会在内存中缓存一些出现频率较高的页面,如果需要访问未存储在内存中的页面,需要去外设中进行访问,并将其导入到内存;但是,由于内存中不可能承载过量的页面,所以,随着页面数量的增多,系统会覆盖掉一些不常用的页面,这个过程也就是常说的页面置换算法。
其中,最为经典的即为LRU(Least Recently Used)算法,该算法的思路是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小,也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
实现LRU缓存机制也是Leetcode上一道经典的算法题,在JAVA中,采用linkedhashmap可以非常方便的实现LRU的缓存,这是因为linkedhashmap的底层数据结构与LRU可实现的数据结构非常类似,都是采用双向链表+hashmap来进行实现。
对LRU缓存的操作,不考虑从外设中读取页面的话,主要包括get和put操作,其中get操作的操作过程为:
- 通过hashmap查找是否存在所要查询的内容,如果存在则继续;
- 获取带查询的内容;
- 从双向链表中删除该节点;
- 将该节点插入到链表头部。
而put操作的过程要区分原链表中包含和不包含待插入的节点的key:
- 通过hashmap查询链表中是否存在待插入节点的key,如果存在,则进入步骤2,不存在则进入步骤3;
- 修改已有节点内的值,从双向链表中删除该节点,并将该节点插入到链表头部;
- 新建一个节点,如果页面存储数量已到达极限,则删除双向链表尾端节点并在hashmap中删除该节点的信息,并将新节点插入到链表头部,同时在hashmap中维护该节点信息。
这里面还主要用到了两个操作,一个是在链表中删除原节点,一个是将节点设置为链表头部。
基于上述思想对LRU机制进行了模拟实现,也是leetcode的第146题,由于网上用java版本实现的较多,而笔者刷题时习惯用C++来写题,于是附用C/C++实现的题解。其中Node中的value为页面内容的模拟,链表中删除原节点和设置链表头部的时候要考虑到链表是否为空和首尾的情况。
struct Node{
int key;
int value;
Node* next;
Node* pre;
Node(int key,int value): key(key),value(value),next(nullptr),pre(nullptr){
}