一、配置Redis内存淘汰策略
maxmemory 100mb
maxmemory-policy allkeys-lru
maxmemory-samples 5
注意:Redis的LRU算法并非完整的实现,而是近似LRU的算法,详细介绍点击这里
二、LRU实现原理
1、双向链表 + 哈希表
1、哈希表:查找快,但是哈希表的数据是乱序的。
2、链表:插入、删除都很快,但是查询慢。
- 1.每次默认从链表头部添加元素,那么显然越靠近头部的元素就越是最近使用的。越靠近尾部的元素就是越久未使用的。
- 2.对于某一个 key ,可以通过哈希表快速定位到链表中的节点,从而取得对应的 value。
- 3.链表显然是支持在任意位置快速插入和删除的,修改指针就行。但是单链表无法按照索引快速访问某一个位置的元素,都是需要遍历链表的,所以这里借助哈希表,可以通过 key,快速的映射到任意一个链表节点,然后进行插入和删除。
1、为什么这里要用双链表呢,单链表为什么不行?
答:双链表删除元素除了自己本身的指针信息,前驱节点的指针和后驱节点的指针。单链表只有后驱节点的指针。
2、哈希表里面已经保存了 key ,那么链表中为什么还要存储 key 和 value 呢,只存入 value 不就行了?
答:删除哈希表中的数据需要通过双链表中的key,才能删除对应value。
三、PHP实现LRU
/**
* LRU算法
* 构造一个双向链表(双向链表删除尾部节点的上一个元素时间复杂度为O(1))
*/
class LRUCache
{
//头部节点
private $head;
//尾部节点
private $tail;
//最大容量,大于淘汰尾部节点指向的上一个元素
private $capacity;
//存放key对应的节点 key => node
private $hashmap;
/**
* 初始化头部尾部节点
* LRUCache constructor.
* @param $capacity
*/
public function __construct($capacity)
{
$this->capacity = $capacity;
$this->hashmap = array();
$this->head = new Node(null, null);
$this->tail = new Node(null, null);
//头结点右指针指向尾结点
$this->head->setNext($this->tail);
//尾结点左指针指向头结点
$this->tail->setPrevious($this->head);
}
/**
* 获取元素
* @param $key
* @return null
*/
public function get($key)
{
if (!isset($this->hashmap[$key])) {
return null;
}
$node = $this->hashmap[$key];
if (count($this->hashmap) == 1) {
return $node->getData();
}
//先删除已经存在的结点
$this->detach($node);
//重新将新结点插入到头结点之后
$this->attach($this->head, $node);
return $node->getData();
}
/**
* 设置key value
* @param $key
* @param $data
* @return bool
*/
public function put($key, $data)
{
if ($this->capacity <= 0) {
return false;
}
if (isset($this->hashmap[$key]) && !empty($this->hashmap[$key])) {
$node = $this->hashmap[$key];
//重置结点到头结点之后
$this->detach($node);
$this->attach($this->head, $node);
$node->setData($data);
} else {
$node = new Node($key, $data);
$this->hashmap[$key] = $node;
//添加节点到头部节点之后
$this->attach($this->head, $node);
//检测容量是否达到最大值
if (count($this->hashmap) > $this->capacity) {
//如果达到最大值 删除尾节点左指针指向的元素
$nodeToRemove = $this->tail->getPrevious();
$this->detach($nodeToRemove);
unset($this->hashmap[$nodeToRemove->getKey()]);
}
}
return true;
}
/**
* 删除key
* @param $key
* @return bool
*/
public function remove($key)
{
if (!isset($this->hashmap[$key])) {
return false;
}
$nodeToRemove = $this->hashmap[$key];
$this->detach($nodeToRemove);
unset($this->hashmap[$nodeToRemove->getKey()]);
return true;
}
/**
* 添加新结点到头结点之后
* @param $head
* @param $node
*/
private function attach($head, $node)
{
//双向链表插入一个元素到头结点之后
$node->setPrevious($head);
$node->setNext($head->getNext());
$node->getNext()->setPrevious($node);
$node->getPrevious()->setNext($node);
}
/**
* 删除结点
* @param $node
*/
private function detach($node)
{
$node->getPrevious()->setNext($node->getNext());
$node->getNext()->setPrevious($node->getPrevious());
}
}
/**
* 结点数据结构
*/
class Node
{
private $key;
//key对应的内容
private $data;
//结点右指针
private $next;
//结点左指针
private $previous;
/**
* Node constructor.
* @param $key
* @param $data
*/
public function __construct($key, $data)
{
$this->key = $key;
$this->data = $data;
}
/**
* 设置节点数据的新值
* Sets a new value for the node data
* @param string the new content of the node
*/
public function setData($data)
{
$this->data = $data;
}
/**
* 将节点设置为下一个节点
* Sets a node as the next node
* @param Node $next the next node
*/
public function setNext($next)
{
$this->next = $next;
}
/**
* 将节点设置为前一个节点
* Sets a node as the previous node
* @param Node $previous the previous node
*/
public function setPrevious($previous)
{
$this->previous = $previous;
}
/**
* 返回节点键
* Returns the node key
* @return string the key of the node
*/
public function getKey()
{
return $this->key;
}
/**
* 返回节点数据
* Returns the node data
* @return mixed the content of the node
*/
public function getData()
{
return $this->data;
}
/**
* 返回下一个节点, 该节点的下一个节点
* Returns the next node
* @return Node the next node of the node
*/
public function getNext()
{
return $this->next;
}
/**
* 返回上一个节点, 该节点的上一个节点
* Returns the previous node
* @return Node the previous node of the node
*/
public function getPrevious()
{
return $this->previous;
}
}
$lru = new LRUCache(4);
$lru->put(1, 1111);
$lru->put(2, 2222);
$lru->put(3, 3333);
//$lru->put(3, 3333);
//$lru->put(4, 4444);
//var_dump($lru->get(2));
//var_dump($lru->get(1));