LRU使用背景
在编写Android程序的时候会用到许多图片,不同的图片可能会有不同的大小,不同的形状,但是在大多数情况下图片都不会大于程序的大小。由于在编写程序的时候有一定的内存限制,在应用程序的UI界面加载一张图片是一件和简单的事情,但是因为需要在界面上加载一大堆的图片时,在很多情况下屏幕上显示的图片可以通过滑动屏幕等事件不断增加,最终导致OOM(OutOfMemory)异常。
LRU应用场景
为了能让程序快速运行起来在界面上迅速地加载图片,但是又必须考虑有些图片被回收之后,用户又将它重新滑入屏幕的情况,这时如果重新加载一遍刚刚加载过的图片会导致性能消耗的问题。
LRU Cache将最近使用的对象用强引用存储在hash表中,并且将最近很少使用的对象在缓存值达到预设定值之前凑合内存中移除。
Cache中的存储空间被分为若干块,每一块对应内存或磁盘文件的一段数据,形成映射关系;当Cache中的存储块被用完,但是新的数据又要存储在Cache中时,这时可以使用LRU算法完成数据块的替换。
LRU(最近最少使用算法)
设计原则:如果一个数据在最近一段时间没有被访问到,那么在将来的一段时间被访问的可能性也很小
问题1:使用什么数据结构可以将旧的数据块记录下来,当Cache中的存储块用完之后,添加新数据的时候准确将旧数据删除
方案: 将Cache中所有存储块的位置使用双向链表连接起来,当一个位置的数据被访问,通过调整链表的指向将这个位置的存储块连接到链表的头部;再经过多次数据访问之后,最近常被访问的数据的存储块就向链表的头部移动,没有被访问到或者被访问的次数少的存储块就向链表的后面移动
记录旧数据块的问题解决之后,找到需要删除旧数据该怎么找?
问题2: 每次新数据需要Cache块进行存储的时候,删除旧数据需要对链表进行遍历,时间复杂度为O(n),那么使用什么数据结构能够提高查找效率
方案: 双向链表中节点的属性_next和_prev指针用于记录前后节点,key用于存储对象的键值,value是存储Cache块对象本身,用hash表来查找具体被命中的Cache块,访问效率为O(1)
问题3:Cache存储块未分配完,新数据被访问,如何将被访问的数据插入到链表头部
方案:
首先去hash表中进行查找;
1. 如果被访问的数据在hash表中存在,则直接将双向链表中的数据移除,再进行头插(保证在预设值范围内);
2. 如果被访问的数据不存在hash表中,则先进行哈希映射将数据添加到hash表中,再将数据头插到双向链表中;
问题4: Cache存储块已经分配完毕,新访问的数据需要分配一个Cache这是需要对旧数据块进行删除操作
方案: 先从双向链表中移除旧数据节点,再从hash表中移除旧数据节点,最后将节点进行删除
总体设计
//缓存节点类
template<class K, class T>
struct LRUnode
{
K _key;
T _data;
LRUnode<K, T>* hash_prev;//指向hash链表的前一个节点
LRUnode<K, T>* hash_next;//指向hash链表的后一个节点
LRUnode<K, T>* list_prev;//指向双向循环链表的前一个节点
LRUnode<K, T>* list_next;//指向双向循环链表的后一个节点
LRUnode<K, T>(const K key, const T data)
: _key(key)
, _data(data)
, hash_prev(NULL)
, hash_next(NULL)
, list_prev(NULL)
, list_next(NULL)
{}
};
//仿函数
template<class K>
struct Isequal
{
bool operator()(K left, K right)
{
if (left == right)
{
return true;
}
return false;
}
};
//缓存类
template<class K, class T, class Compare = Isequal<K>>
class LRUcache
{
typedef LRUnode<K, T> Node;
public:
LRUcache(size_t capacity);
~LRUcache();
//向缓存中添加数据
void LRUcacheSet(K key, T data);
//从缓存中取数据
bool LRUcacheGet(K key, T& data);
private:
//从双向链表中删除指定节点
void RemoveFromList(Node* node);
//从哈希数组中删除指定节点
void RemoveFromHash(Node* node);
//向双向链表表头插入节点
LRUnode<K, T>* InsertValueToList(Node* node);
//向哈希数组中插入一个缓存单元
void InsertValueToHash(Node* node);
//更新双向链表
void UpdatecacheList(Node* node);
//删除整个双向链表
void DeleteList(Node* head);
//利用哈希函数映射,获取hash值
int HashFunc(K key);
//从hashmap中获取一个缓存单元
LRUnode<K, T>* GetValueFromHashmap(K key);
private:
size_t _capacity;//缓存的容量
Node** _Hashmap;//哈希数组--方便进行查找
Node* _cachelistHead;//双向缓存链表的头部
Node* _cachelistTail;//双向缓存链表的尾部
size_t _cachelistsize;//双向缓存链表中节点的个数
};