LRU算法

LRU介绍

LRU算法全称Least Recently Used, 最近最少使用的意思,是一种内存管理的算法,最早应用于Linux操作系统

LRU算法基于一种假设:长期不被使用的数据,在未来被使用到的几率也不大,因此,当数据所占内存达到一定的阈值时,我们需要移除最近最少被使用的数据。

hash链表

在LRU算法中,使用了一种有趣的数据结构,这种数据结构叫做哈希链表。

哈希表是由若干个key-value所组成。在逻辑上这些key-value是无所谓顺序的。在哈希链表中这些key-value不再是彼此无关的存在,而是被一个链表串在一起。每一个key-value都具有它的前驱key-value, 后继key-value. 就像是双向链表中的节点一样。这样原本无序的哈希表就拥有了固定的排列顺序。

我们以用户信息的需求为例,来演示LRU算法的基本思路:

  1. 假设我们使用哈希链表来缓存用户信息。目前缓存了4个用户,这4个用户是按照时间顺序依次从链表右端插入。

    1 <--> 2 <--> 3 <--> 4
    
  2. 此时,业务方访问了用户5,由于哈希链表中没有用户5的数据,我们从数据库中读取出来,插入到缓存当中。这时候链表中最右端是最新访问到的用户5,最左端是最近最少访问的用户1.

    1 <--> 2 <--> 3 <--> 4 <--> 5
    
  3. 接下来,业务方访问用户2,hash链表中存在用户2的数据。我们把用户2从它的前驱节点和后继节点中移除,重新插入到链表的最右侧。这时候,链表的最右侧就变成了最新访问到的用户2。最左端依然是最近最少访问到的用户1.

    1 <--> 3 <--> 4 <--> 5 <--> 2
    
  4. 接下来,业务方请求修改用户4的信息。同样的道理,我们把用户4从原来位置上摘下来拿到链表的最右侧,并把用户信息进行更新。此时链表最右端是最新更新访问到的用户4,最左端依然是用户1.

    1 <--> 3 <--> 5 <--> 2 <--> 4
    
  5. 后来业务需要访问用户6,用户6没有在缓存中。需要插入到hash链表。假设此时缓存容量已经达到了上限,必须先删除最近最少访问的数据,那么位于hash链表最左端的用户1,将被删除掉,用户6插入到最右边。

    3 <--> 5 <--> 2 <--> 4 <--> 6
    

代码实现

/*
  Copyright (c) 2019 Sogou, Inc.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  Authors: Wu Jiaxu (wujiaxu@sogou-inc.com)
*/

#ifndef _LRUCACHE_H_
#define _LRUCACHE_H_
#include <assert.h>
#include <map>
#include <mutex>
//#include <unordered_map>

/**
 * @file   LRUCache.h
 * @brief  Template LRU Cache
 */

// RAII: NO. Release ref by LRUCache::release
// Thread safety: NO.
// DONOT change value by handler, use Cache::put instead
template<typename KEY, typename VALUE>
class LRUHandle
{
public:
	VALUE value;

private:
	LRUHandle():
		prev(NULL),
		next(NULL),
		ref(0)
	{}

	LRUHandle(const KEY& k, const VALUE& v):
		value(v),
		key(k),
		prev(NULL),
		next(NULL),
		ref(0),
		in_cache(false)
	{}
	//ban copy constructor
	LRUHandle(const LRUHandle& copy);
	//ban copy operator
	LRUHandle& operator= (const LRUHandle& copy);
	//ban move constructor
	LRUHandle(LRUHandle&& move);
	//ban move operator
	LRUHandle& operator= (LRUHandle&& move);

	KEY key;
	LRUHandle *prev;
	LRUHandle *next;
	int ref;
	bool in_cache;

template<typename, typename, class> friend class LRUCache;
};

// RAII: NO. Release ref by LRUCache::release
// Define ValueDeleter(VALUE& v) for value deleter
// Thread safety: YES
// Make sure KEY operator< usable
template<typename KEY, typename VALUE, class ValueDeleter>
class LRUCache
{
protected:
typedef LRUHandle<KEY, VALUE>			Handle;
typedef std::map<KEY, Handle*>			Map;
typedef typename Map::iterator			MapIterator;
typedef typename Map::const_iterator	MapConstIterator;

public:
	LRUCache():
		max_size_(0),
		size_(0)
	{
		not_use_.next = &not_use_;
		not_use_.prev = &not_use_;
		in_use_.next = &in_use_;
		in_use_.prev = &in_use_;
	}

	~LRUCache()
	{
		std::lock_guard<std::mutex> lock(mutex_);

		// Error if caller has an unreleased handle
		assert(in_use_.next == &in_use_);
		for (Handle *e = not_use_.next; e != &not_use_; )
		{
			Handle *next = e->next;

			assert(e->in_cache);
			e->in_cache = false;
			assert(e->ref == 1);// Invariant for not_use_ list.
			unref(e);
			e = next;
		}
	}

	// default max_size=0 means no-limit cache
	// max_size means max cache number of key-value pairs
	void set_max_size(size_t max_size)
	{
		std::lock_guard<std::mutex> lock(mutex_);

		max_size_ = max_size;
	}

	size_t get_max_size() const { return max_size_; }
	size_t size() const { return size_; }

	// Remove all cache that are not actively in use.
	void prune()
	{
		std::lock_guard<std::mutex> lock(mutex_);

		while (not_use_.next != &not_use_)
		{
			Handle *e = not_use_.next;

			assert(e->ref == 1);
			cache_map_.erase(e->key);
			erase_node(e);
		}
	}

	// release handle by get/put
	void release(Handle *handle)
	{
		if (handle)
		{
			std::lock_guard<std::mutex> lock(mutex_);

			unref(handle);
		}
	}

	void release(const Handle *handle)
	{
		release(const_cast<Handle *>(handle));
	}

	// get handler
	// Need call release when handle no longer needed
	const Handle *get(const KEY& key)
	{
		std::lock_guard<std::mutex> lock(mutex_);
		MapConstIterator it = cache_map_.find(key);

		if (it != cache_map_.end())
		{
			ref(it->second);
			return it->second;
		}

		return NULL;
	}

	// put copy
	// Need call release when handle no longer needed
	const Handle *put(const KEY& key, VALUE value)
	{
		Handle *e = new Handle(key, value);

		e->ref = 1;
		std::lock_guard<std::mutex> lock(mutex_);

		/// max_size_ == 0 代表无限容量
		if (max_size_ == 0 || size_ < max_size_)
		{
			size_++;
			e->in_cache = true;
			e->ref++;
			list_append(&in_use_, e);
			MapIterator it = cache_map_.find(key);
			if (it != cache_map_.end())
			{
				erase_node(it->second);
				it->second = e;
			}
			else
				cache_map_[key] = e;
		}
		else//do not cache
			e->next = NULL;

		if (max_size_ > 0)
		{
			while (size_ >= max_size_ && not_use_.next != &not_use_)
			{
				Handle *old = not_use_.next;

				assert(old->ref == 1);
				cache_map_.erase(old->key);
				erase_node(old);
			}
		}

		return e;
	}

	// delete from cache, deleter delay called when all inuse-handle release.
	void del(const KEY& key)
	{
		std::lock_guard<std::mutex> lock(mutex_);
		MapConstIterator it = cache_map_.find(key);

		if (it != cache_map_.end())
		{
			Handle *node = it->second;

			cache_map_.erase(it);
			erase_node(node);
		}
	}

private:
	void list_remove(Handle *node)
	{
		node->next->prev = node->prev;
		node->prev->next = node->next;
	}

	void list_append(Handle *list, Handle *node)
	{
		node->next = list;
		node->prev = list->prev;
		node->prev->next = node;
		node->next->prev = node;
	}

	void ref(Handle *e)
	{
		if (e->in_cache && e->ref == 1)
		{
			list_remove(e);
			list_append(&in_use_, e);
		}

		e->ref++;
	}

	void unref(Handle *e)
	{
		assert(e->ref > 0);
		e->ref--;
		if (e->ref == 0)
		{
			assert(!e->in_cache);
			value_deleter_(e->value);
			delete e;
		}
		else if (e->in_cache && e->ref == 1)
		{
			list_remove(e);
			list_append(&not_use_, e);
		}
	}

	void erase_node(Handle *e)
	{
		assert(e->in_cache);
		list_remove(e);
		e->in_cache = false;
		size_--;
		unref(e);
	}

	std::mutex mutex_;
	size_t max_size_;
	size_t size_;

	Handle not_use_;
	Handle in_use_;
	Map cache_map_;

	ValueDeleter value_deleter_;
};

#endif  // SSS_LRUCACHE_H_
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Erice_s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值