双链表实现LRU缓存淘汰策略

Posted 不舍昼夜夫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了双链表实现LRU缓存淘汰策略相关的知识,希望对你有一定的参考价值。

1.背景

LRU(Least Recently Used)是一种常用的缓存淘汰策略,即当缓存满了之后,删除最近最少使用的数据。 

LRU的实现,常用的编程语言在语言层面都有实现,可以直接使用。为了深入理解LRU,本文介绍一种通过双链表实现LRU方法。

2.双链表实现方法

Java版双链表实现代码。

2.1 定义节点实体类

/**
* 链表节点.
* @author liunan1 .
*/
public class Node {
private int key;

private int value;

private Node next;

private Node prev;

public Node(int key, int value) {
this.key = key;
this.value = value;
}

public int getKey() {
return key;
}

public int getValue() {
return value;
}

public Node getNext() {
return next;
}

public void setNext(Node next) {
this.next = next;
}

public Node getPrev() {
return prev;
}

public void setPrev(Node prev) {
this.prev = prev;
}
}

2.2 双链实现类

package lewis.test.algorithm.lru.model;

import java.util.Objects;

/**
* 双链表.
* @author liunan1 .
*/
public class IntList {
private int length;
private Node head;
private Node tail;

/**
* 删除最后一个节点.
*/
public void removeLast() {
if (Objects.nonNull(tail) && Objects.nonNull(tail.getPrev())) {
Node prev = tail.getPrev();
prev.setNext(null);
tail = prev;
--length;
}
}

/**
* 查找数据,未找到返回-1.
* @param key
* @return
*/
public int get(int key) {
int i = 1;
Node currentNode = head;
for(; i <= length; i++) {
int findKey = currentNode.getKey();
int value = currentNode.getValue();
Node prev = currentNode.getPrev();
Node next = currentNode.getNext();
if (findKey == key) {
// 将节点移动到链表头部
if (Objects.nonNull(prev)) {
// 1.将节点移除
prev.setNext(next);
if (Objects.nonNull(next)) {
next.setPrev(prev);
} else {
// 节点队尾,更新队尾指针.
tail = prev;
}
// 2. 将节点移动到表头
currentNode.setNext(head);
head.setPrev(currentNode);
currentNode.setPrev(null);
head = currentNode;
}
return value;
}
currentNode = next;
}
// 未找到
return -1;
}

/**
* 头部插入节点.
* @param node node.
*/
public void addHead(Node node) {
head.setPrev(node);
node.setNext(head);
head = node;
++length;
}

/**
* 链表长度.
* @return 长度.
*/
public int size() {
return length;
}
}

2.3 LRU模拟实现类

package lewis.test.algorithm.lru.model;

import java.util.Random;

/**
* LRU缓存淘汰策略实现类.
* @author liunan1 .
*/
public class LeastRecentlyUsedCache {
// 缓存容量
private int capacity;
private IntList list;


public LeastRecentlyUsedCache(int capacity) {
this.capacity = capacity;
}

/**
* 从缓存中获取数据.
* @param key .
* @return
*/
public int get(int key) {
int value = this.list.get(key);
// 未找到,从磁盘读取.
if (-1 == value) {
// 模拟从磁盘读取.
value = new Random().nextInt(100);
// 插入缓存.
put(key, value);
}
return value;
}

/**
* 插入数据.
* @param key key.
* @param value value.
*/
public void put(int key, int value) {
Node node = new Node(key, value);
list.addHead(node);

if (list.size() > capacity) {
list.removeLast();
}
}

}

3. 总结与思考

通过三个Java类,模拟实现了LRU,简单明了,易于理解。用心的读取也许已经发现,该实现查找缓存的时候,性能不够好,时间复杂度是O(n)。后续将对该实现进行优化,将时间复杂度提升到O(1)。


以上是关于双链表实现LRU缓存淘汰策略的主要内容,如果未能解决你的问题,请参考以下文章

06 | 链表(上):如何实现LRU缓存淘汰算法?

从源码出发,分析LRU缓存淘汰策略的实现!

面试题LRU算法及编码实现LRU策略缓存

看动画理解「链表」实现LRU缓存淘汰算法

缓存淘汰策略之LRU

动手写一个LRU缓存