Redis LRU 策略
使用 redis 作为缓存,当添加新数据时,若有内存大小等限制,系统默认会根据一定的规则自动清理旧数据。
LRU(Least Recently Used,即最近最久未使用)实际上只是 Redis 支持的内存回收策略中的一种。其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
Redis 的 maxmemory 配置选项用来限制 Redis 的内存使用大小,当实际内存超出 maxmemory 时,Redis 提供了几种策略来让用户决定如何腾出新的空间。
这六种策略分别是:
- volatile-lru:使用 LRU 算法删除带有过期设置的 key
- allkeys-lru:使用 LRU 算法删除任意 key
- volatile-random:随机删除带有过期设置的 key
- allkeys-random:随机删除任意 key
- volatile-ttl:删除过期时间最近的 key
- noeviction:不过期,在执行写入操作时返回一个错误
Java 基于双向链表和 Map 集合的 LRUCache 实现
package com.lvshui5u.algorithms.basis;
import java.util.HashMap;
import java.util.Map;
/**
* @author: lvshui5u
* @date: 2021/7/18 19:30
* @describe:
*/
public class LRUCache<K, V> {
/**
* 缓存容量
*/
private final int capacity;
/**
* 头、尾节点
*/
private Node<K, V> head;
private Node<K, V> tail;
/**
* LRU 字典,用来存储 Node 节点
*/
private final Map<K, Node<K, V>> map;
/**
* 构造函数
* @param capacity LRU 最大容量
*/
public LRUCache(int capacity){
this.capacity = capacity;
this.map = new HashMap<>(capacity);
}
public void put(K key, V value){
Node<K, V> node = map.get(key);
if(node != null){
// 该 node 存在在 map 和双向链表里
// 将其移动到链表头部
node.value = value;
moveToHead(node);
}else {
// 该 node 不存在 map 和双向链表里,new 出来
node = new Node<>();
node.key = key;
node.value = value;
if(capacity == map.size()){
// 容量已满
// 把链表尾部元素删除
removeTail();
}
// 把该元素添加到头部
addToHead(node);
map.put(key,node);
}
}
public V get(K key){
Node<K, V> node = map.get(key);
if(node == null){
return null;
}
// 提到队首
moveToHead(node);
return node.value;
}
private void removeTail() {
// 1、从 map 中移除
map.remove(tail.key);
///2、从链表中移除
if(tail.pre != null){
tail = tail.pre;
tail.next = null;
}else {
head = tail = null;
}
}
private void addToHead(Node<K, V> node) {
if(head == null){
head = tail = node;
}else {
node.next = head;
head.pre = node;
head = node;
}
}
private void moveToHead(Node<K, V> node) {
if(node == head){
return;
}
// 先断
Node<K, V> pre = node.pre;
Node<K, V> next = node.next;
pre.next = next;
if(next!=null){
// 此时的 node 为尾,又挪到前面了,只能 pre 为尾
next.pre = pre;
}else{
tail = pre;
}
// 再连
node.next = head;
head.pre = node;
node.pre = null;
head = node;
}
public void show(){
Node<K, V> cur = head;
while (cur != null){
System.out.print(cur.value+"->");
cur = cur.next;
}
System.out.println();
}
/**
* 节点类
* @param <V> 值
*/
private static class Node<K, V>{
K key;
V value;
Node<K, V> pre;
Node<K, V> next;
}
}
测试
public class Test {
public static void main(String[] args) {
LRUCache<Integer, Integer> lruCache = new LRUCache<>(16);
for (int i = 0; i < 16; i++) {
lruCache.put(i,i);
}
lruCache.show();
System.out.println("get "+lruCache.get(5)+" 之后: ");
lruCache.show();
System.out.println("get "+lruCache.get(10)+" 之后: ");
lruCache.show();
System.out.println("get "+lruCache.get(1)+" 之后: ");
lruCache.show();
lruCache.put(20,20);
System.out.println("put 20 之后");
lruCache.show();
lruCache.put(55,55);
System.out.println("put 55 之后");
lruCache.show();
lruCache.put(100,100);
System.out.println("put 100 之后");
lruCache.show();
}
}
结果
15->14->13->12->11->10->9->8->7->6->5->4->3->2->1->0->
get 5 之后:
5->15->14->13->12->11->10->9->8->7->6->4->3->2->1->0->
get 10 之后:
10->5->15->14->13->12->11->9->8->7->6->4->3->2->1->0->
get 1 之后:
1->10->5->15->14->13->12->11->9->8->7->6->4->3->2->0->
get 0 之后:
0->1->10->5->15->14->13->12->11->9->8->7->6->4->3->2->
put 20 之后
20->0->1->10->5->15->14->13->12->11->9->8->7->6->4->3->
put 55 之后
55->20->0->1->10->5->15->14->13->12->11->9->8->7->6->4->
put 100 之后
100->55->20->0->1->10->5->15->14->13->12->11->9->8->7->6->
Process finished with exit code 0