操作系统PageIn、PageOut所用到的LRU策略(原理+实现)

本文介绍了LRU(Least Recently Used)缓存淘汰策略,详细讲解了其基于双向链表和哈希表的实现原理,包括数据访问更新、插入与淘汰过程,并通过示意图辅助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是LRU缓存策略
LRU(Least Recently Used)近期最少使用算法。它的原理就是,缓存一定量的数据,当缓存数量超过设置的阈值时就删除一部分旧的数据。
那么我们怎样判定旧数据呢???根据局部性原理,距离当前最久没有被访问过的数据应该被淘汰。

二、LRU缓存策略实现原理
1、使用双向链表记录数据的被使用的时间
因为我们要删除最久没有被访问的数据,为了保证效率,我们在缓存中维护一个双向链表,该链表将缓存中的数据按照访问时间从新到旧排列起来。
当我们需要访问一个数据的时候,如果缓存中已经缓存了这个数据,那么我们就将该数据从缓存的双向链表中摘除,然后再重新放入到双向链表的表头。
如果缓存中没有我们需要的数据,那么我们可以在外部获得数据,然后将数据放入到缓存中,此过程中会将新数据插入到双向链表的表头。

2、使用hash表保证缓存中数据的访问速度
因为链表进行查找是O(n),比较慢。因此为了提高效率,我们除了将数据维护在一个双向链表中,同时还将数据维护在一个hash表中,这时我们访问的效率就变成了O(1)。

3、原理如图:
这里写图片描述
首先明白,hash表和双向链表用的是同一个结点。双向链表按照访问时间将这些结点串起来,就是图中黑色的箭头。hash表按照key值将这些结点串起来,并且使用拉链法解决冲突,比如紫色的箭头。

4、向缓存中插入一个结点
首先到hash表中去找:
如果找到的话,再去双向链表中将这个结点移到双向链表的头部。
如果没找到的话,则将这个数据插入到双向链表的头部,同时也插入到hash表中。如果这时候缓存的数量已经超过阈值,则就将双向链表的最后一个结点从链表中移除(注意不是删除),然后再将这个移除的结点,从hash表也移除,最后再删除这个结点。因为hash表和双向链表使用的是同一个结点,所以必须等到两边都移除后才能删除。

三、模拟实现LRU缓存

#include<iostream>
#include<iomanip>
using namespace std;
 
//缓存链表、hash表的结点类型
typedef struct SNode{
	int iKey;
	char cData;//此处可能是1K的数据量
	//【出于演示效果和运行效率使用char来代替】
	SNode* pHashPrev;//指向hash链表的前一个结点
	SNode* pHashNext;//指向hash链表的后一个结点
	SNode* pLstPrev;//指向缓存双向链表的前一个结点
	SNode* pLstNext;//指向缓存双向链表的后一个结点
	SNode(int key, char data){
		cout << "SNode构造函数:" << key << endl;
		iKey = key; cData = data;
		pHashPrev = pHashNext = NULL;
		pLstPrev = pLstNext;
	}
	~SNode(){cout << "SNode析构函数:" << iKey << endl;}
}SNODE, *PSNODE, **PPSNODE;
 
//缓存类
class CCache{
private:
	int iCap;//缓存的容量
	PPSNODE pHashTable;//指针数组
	PSNODE pLstHead;//指向缓存双向链表的头部
	PSNODE pLstTail;//指向缓存双向链表的尾部
	int iLstLen;//记录双向链表结点中的个数
public:
	CCache(int cap){
		iCap = cap;
		pHashTable = new SNODE*[iCap];
		memset(pHashTable, 0, sizeof(SNODE*)*iCap);
		pLstHead = pLstTail = NULL; iLstLen = 0;
	}
	~CCache(){
		DelLst(pLstHead);
		delete[] pHashTable; pHashTable = NULL;
		pLstHead = pLstTail = NULL; iLstLen = 0;
	}
	void Set(int key, char data){//向缓存中放入数据
		//从HashTable中获取一个缓存单元
		PSNODE pNode = GetFromHashTable(key);
		if(pNode == NULL){//缓存不在HashTable中
			pNode = new SNODE(key, data);//创建一个新的缓存
			InsertIntoHashTable(pNode);//将缓存结点插入到HashTable中
			PSNODE pBack = InsertIntoList(pNode);//将缓存结点插入到链表头部
			if(pBack != NULL){//插入链表的过程中发生数据溢出
				RemoveFromHashTable(pBack);//从HashTable中移除这个单元
				delete pBack;//PageOut : pBack; PageIn : pNode;
			}
		}
		else			UpdateList(pNode);
	}
	bool Get(int key, char& data){//从缓存中得到数据
		//从HashTable中获取一个缓存单元	
		PSNODE pNode = GetFromHashTable(key);
		if(pNode != NULL){
			UpdateList(pNode);
			data = pNode->cData;
			return true;
		}
		return false;
	}
	void DispLst(){
		cout << "*****************************************************" << endl;
		PSNODE pNode = pLstHead;
		while(pNode != NULL){
			cout << "(" << pNode->iKey << ", " << pNode->cData << ")--->";
			pNode = pNode->pLstNext;
		}
		cout << "null" << endl << "*****************************************************" << endl;
	}
	void DispHashTable(){
		PSNODE pNode = NULL;
		for(int i = 0; i < iCap; i++){
			pNode = pHashTable[i];
			cout << "[" << setw(2) << i << "] : ";
			while(pNode != NULL){
				cout << "(" << pNode->iKey << ", " << pNode->cData << ")--->";
				pNode = pNode->pHashNext;
			}
			cout << "null" << endl;
		}
		cout << "*****************************************************" << endl;
	}
private:
	void RemoveFromList(PSNODE pDel){//从双向链表中移除指定结点
		if(pDel == NULL)			return;
		if(pLstHead == pLstTail)//链表只有一个结点
			pLstHead = pLstTail = NULL;
		else if(pLstHead == pDel){//删除头指针
			pLstHead = pDel->pLstNext;
			pLstHead->pLstPrev = NULL;
		}
		else if(pLstTail == pDel){//删除尾指针
			pLstTail = pDel->pLstPrev;
			pLstTail->pLstNext = NULL;
		}
		else{//删除中间
			pDel->pLstPrev->pLstNext = pDel->pLstNext;
			pDel->pLstNext->pLstPrev = pDel->pLstPrev;
		}
		--iLstLen;
	}
	PSNODE InsertIntoList(PSNODE pNew){//向双向链表的表头插入数据
		PSNODE pLastNode = NULL;
		if(++iLstLen > iCap){//PageOut
			pLastNode = pLstTail;
			RemoveFromList(pLastNode);
		}
		if(pLstHead == NULL){
			pLstHead = pLstTail = pNew;
			pNew->pLstPrev = pNew->pLstNext = NULL;
		}
		else{
			pNew->pLstNext = pLstHead;
			pLstHead->pLstPrev = pNew;
			pLstHead = pNew;
			pNew->pLstPrev = NULL;
		}
		return pLastNode;
	}
	void UpdateList(PSNODE pCurr){
		RemoveFromList(pCurr);//从双向链表中移除指定结点
		InsertIntoList(pCurr);//将缓存结点插入到链表头部
	}
	void DelLst(PSNODE pHead){
		PSNODE pSearch = pHead, pFollow = pHead;
		while(pSearch != NULL){
			pFollow = pSearch;
			pSearch = pSearch->pLstNext;
			delete pFollow;
		}
	}
	PSNODE GetFromHashTable(int key){//从HashTable中获取一个缓存单元
		PSNODE pCurr = pHashTable[key%iCap];
		while(pCurr != NULL){
			if(key == pCurr->iKey)			return pCurr;
			pCurr = pCurr->pHashNext;
		}
		return NULL;
	}
	void InsertIntoHashTable(PSNODE pNew){//插入一个缓存单元到HashTable
		PSNODE pCurr = pHashTable[pNew->iKey%iCap];
		if(pCurr == NULL){
			pHashTable[pNew->iKey%iCap] = pNew;
			pNew->pHashPrev = pNew->pHashNext = NULL;
		}
		else{
			pNew->pHashNext = pCurr;
			pCurr->pHashPrev = pNew;
			pHashTable[pNew->iKey%iCap] = pNew;
			pNew->pHashPrev = NULL;
		}
	}
	void RemoveFromHashTable(PSNODE pDel){//从HashTable中移除一个缓存单元
		if(pDel == NULL)				return;
		if(pDel->pHashPrev == NULL){//要移除的结点在链表的首部
			pHashTable[pDel->iKey%iCap] = pDel->pHashNext;
			if(pDel->pHashNext)				pDel->pHashNext->pHashPrev = NULL;
		}
		else{
			pDel->pHashPrev->pHashNext = pDel->pHashNext;
			if(pDel->pHashNext)				pDel->pHashNext->pHashPrev = pDel->pHashPrev;
		}
	}
};
 
 
void CacheTst()
{
	cout << "最近最久未被使用被替换策略。" << endl;
	cout << "缓存oCache中始终保持只有oCache.iCap个元素。" << endl;
	cout << "第oCache.iCap+1个元素会把链表中最后一个元素替换掉。" << endl;
	CCache oCache(7);
	oCache.Set(1, 'a');
	oCache.Set(3, 'b');
	oCache.Set(6, 'c');
	oCache.Set(8, 'd');
	oCache.Set(10, 'e');
	oCache.Set(15, 'f');
	oCache.Set(21, 'g');
	cout << endl << endl << "缓存已满!" << endl;
 
	oCache.DispLst();
	oCache.DispHashTable();
 
	cout << endl << endl << "最近被访问的结点会被提升到链表头部!" << endl;
	char ch;
	cout << "Page3 : " << (oCache.Get(3, ch) ? "命中" : "缺页") << endl;
	cout << "Page4 : " << (oCache.Get(4, ch) ? "命中" : "缺页") << endl;
	oCache.DispLst();
	oCache.DispHashTable();
 
	cout << endl << endl << "最久未被访问的结点(链表尾部)会被新结点给替换掉!" << endl;
	oCache.Set(7, 'h');
	oCache.DispLst();
	oCache.DispHashTable();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值