C++实现最基本的LRUCache服务器缓存
2014-03-06 15:40
399 查看
后台开发必备知识,不过我不是搞这个的,只是因为很久以前就想写这些东西,事情多,拖到现在。写的过程里面发现很多问题,不会全部说,最后会顺带提一提。
注意,本篇笔记只是对接口写法做了记录,并没有进行更严格的设计和限制,包括更严密的封装,这里只是学习它实现的原理。
不过有些idea还是要知道的,系统定时对缓存进行清除并加入满足条件的新数据,是根据:访问时间,访问次数,可用缓存容量(分配到的内存)等因素决定的,实际设计其实很多东西需要考虑。
一、介绍:
LRU,Least Recently Used,最近最少使用,服务器缓存常用算法的一种。比如说一些系统登录的操作,不可能每次你访问系统都去调用数据库的东西,如果能划出一些空间来,比如说500M,用来缓存这些东西,这样用户访问的时候先在缓存里找,找不到,再去访问数据库,同时把被访问的内容放到缓存里面(我们可以假设这些东西还会经常被访问)。然而,我们分配用来做缓存(Cache)的空间肯定是有限的,总不可能从数据库读的东西全部放到缓存里,所以,当缓存里的内容达到上限值的时候,我们就要把最少使用的东西写回数据库,再将新的访问内容从数据库暂存到缓存里面。
二、数据结构:
最常用的数据结构实现方式是hash_map和Double-Linked List,hash_map只是为了实现键值key-value对应,这样就避免了每次寻找特定值都要在双线链表里面顺序查找,服务器是没办法负担这种低效率的查找方法的。我们可以为链表节点写一个结构体,用来定义节点的类型;然后专门写一个类用来组织缓存信息的存放——以双链表的形式。
template<class K, class T> struct Node { K key; T data; Node *next; Node *prev; };
template<class K,class T> class LRUCache { public: LRUCache(size_t size); //typedef unsigned int size_t ~LRUCache(); void Put(K key,T data); T Get(K key); private: void Attach(Node<K,T>* node); void Detach(Node<K,T>* node); private: hash_map<K,Node<K,T>*> hashmap; vector<Node<K,T>*> linkedList; Node<K,T>* head; Node<K,T>* tail; Node<K,T>* entries; //temp nodes };
看代码太枯燥,我画了个UML图:
#include <iostream>
#include<ext/hash_map>
#include<vector>
#include<assert.h>
#include<string>
#include<stdlib.h>
#include<stdio.h>
#include<iomanip>
using namespace std;
//grep -R "hash_map" /usr/include . Find the file and you will know what the namespace is.
using namespace __gnu_cxx;
template<class K, class T> struct Node { K key; T data; Node *next; Node *prev; };
template<class K,class T>
class LRUCache
{
public:
LRUCache(size_t size) //typedef unsigned int size_t
{
entries = new Node<K,T>[size];
assert(entries!=NULL);
for(int i = 0; i < size; i++)
{
linkedList.push_back(entries+i); //Store the addr.
}
//Initial the double linklist
head = new Node<K,T>;
tail = new Node<K,T>;
head->prev = NULL;
head->next = tail;
tail->next = NULL;
tail->prev = head;
}
~LRUCache()
{
delete head;
delete tail;
delete []entries;
}
void Put(K key,T data)
{
Node<K,T> *node = hashmap[key];
if(node == NULL) //node == NULL means it doesn't exist
{
if(linkedList.empty()) //linkedList is empty means all avaliable space have been allocated
{
node = tail->prev; //Detach the last code
Detach(node);
hashmap.erase(node->key);
}
else
{
node = linkedList.back(); //Allocate an addr to the node
linkedList.pop_back(); //Pop the addr mentioned above
}
node->key = key;
node->data = data;
hashmap[key] = node;
Attach(node);
}
else
{
Detach(node);
node->data = data;
Attach(node);
}
}
T Get(K key)
{
Node<K,T> * node = hashmap[key];
if(node)
{
Detach(node);
Attach(node); //Attach the node to the fisrt place
return node->data;
}
else
{
return T();
/*U can write some codes to test it:
*void main()
*{
*char c = int();
cout<<c<<endl;
int i = char();
cout<<i<<endl;
cout<<char()<<endl;
cout<<int()<<endl;
}*/
}
}
private:
void Attach(Node<K,T>* node)
{
assert(node != NULL);
node->next = head->next;
node->next->prev = node;
node->prev = head;
head->next = node;
}
void Detach(Node<K,T>* node)
{
assert(node != NULL);
node->prev->next = node->next;
node->next->prev = node->prev;
}
private:
hash_map<K,Node<K,T>*> hashmap;
vector<Node<K,T>*> linkedList;
Node<K,T>* head;
Node<K,T>* tail;
Node<K,T>* entries; //temp nodes
};
int main()
{
cout << "Hello World!" << endl;
LRUCache<int,string> cache(10);
/*
string str = "test";
char buffer[10];
int i = 1;
//itoa(1,c,10); //Not existing in the ANSI-C or C++.
sprintf(buffer,"%d",i); //The alternative to the code above.
str.append(buffer);
cout<<str<<endl;
*/
cache.Put(1,"test1");
cout<<cache.Get(1)<<endl;
if(cache.Get(2)=="")
{
cout<<"Node doesn't exist!"<<endl;
}
cache.Put(0,"test0");
cache.Put(3,"test3");
cache.Put(1,"test_1_again");
cache.Put(12,"test12");
cache.Put(56,"test56");
cout<<cache.Get(3)<<endl;
/*Error code. And I don't know why!!!!
string str="test";
char buffer[10];
for(int i = 0 ; i < 14; i++)
{
if(sprintf(buffer,"%d",i)<10)
{
str.append(buffer);
cache.Put(i,str);
str="test";
}
}
for(int i =0; i < 14; i++)
{
if(!(i/5))
{
cout<<cache.Get(i)<<setw(4);
}
cout<<endl;
}*/
return 0;
}
LRUCache
调试的时候我发现了一个问题:
坑爹了,全部节点的next指针全部都指向自己,这样的话链表长得像什么样子呢?应该是这样:
这个错误到底是怎么来的?
我反复地看代码,节点的链入(Attach)和取出(Detach)都是没有问题的,而且,插入新节点的时候,已经插入过的节点为什么没有了?Attach方法既然是正确的,那为什么节点的next为什么会指向自己?综合上面两个问题,我突然意识到:那只能是分配地址的时候出现问题了!
所以回到构造函数分配地址的部分,我发现在for循环里面,本应是:
linkedList.push_back(entries+i);
这样就能顺序存储分配好的地址。
但我竟然把i写成了1,所以每个地址都成了同一个!吐血的经历。
最后代码更正之后即可正确运行:
对应的测试代码为:
最后一笔带过编写过程中遇到的三个主要问题:
1、make install 源码包安装的软件怎么卸载(最好用--prefix=""指定安装路径,这样你卸载的时候直接卸载那个安装路径的文件夹);
2、QtCreator调试的时候为什么不显示变量(因为你用的python版本是3.0以上的,Qt的gdb调试器不支持,自己重新装一个2.X版本的,参考链接:http://blog.hostilefork.com/qtcreator-debugger-no-locals-ubuntu/);
3、测试代码的时候出现一点小错误,搞了很久但还是不知道为什么(在main函数中注释的最后一段代码,有兴趣的可以自己调试一下)。
参考文章:
1、http://www.cs.uml.edu/~jlu1/doc/codes/lruCache.html
2、/article/1352189.html
相关文章推荐
- 排序算法用C++的基本算法实现十个数排序
- c++实现的常见缓存算法和LRU
- DNS服务器概念的简单的介绍,与搭建一个简单的DNS名称缓存服务器,实现域名解析(一)
- thrift编写服务器/客户端实现图片传输(c++)
- C/C++:各种基本算法实现小结(一)—— 单链表
- 循环缓存区之C++实现
- Android性能优化之实现双缓存的图片异步加载工具(LruCache+SoftReference) - 拿来即用
- 基本的HTML文本解析器的设计和实现(C/C++源码)--转csdn
- c++中string类的基本实现
- 用C++&递归的方法实现二叉树的基本功能~
- 用c++实现在局域网中客户机端对服务器数据库的访问
- C++之ACE实现通用服务器的C/S架构通信程序
- c++实现基本栈造作
- 服务器负载均衡的基本功能和实现原理
- CDHtmlDialog的基本使用(C++调用JS函数的实现)
- c++实现数据缓存(包含存储定长变长整数)
- C++与QML实现交互的两种基本方式
- 利用缓存实现APP端与服务器接口交互的Session统制
- cpu占用率为正弦曲线(C#实现)基本照抄书上的C++
- 实现C++基本内容之构造函数、析构函数和赋值函数