LRU 算法
LRU 是一种作为缓存的算法,像 CPU 缓存,数据库缓存,浏览器缓存。以及在移动端开发时的图片安缓存,采用 LRU 缓存策略的应用很广泛。在面试中也是常常考察的一个点。当然也有其他缓存方法,常见的策略有三种:先进先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。
下面就来一起实现一下 LRU 算法。
实现
主要思路:采用链式结构,越早加入到链中数据,顺序越靠近尾部,后来加入的数据添加到头部。
当开始需要访问数据时,先遍历链表,分两种情况:
(1)存在数据
- 数据在头部,则直接返回,不需要做操作
- 数据不在头部,将数据移动到头部(注意在尾部的情况)
(2)不存在数据
- 达到达到最大容量,删除尾部的一个元素,然后添加新元素到头部
- 未达到最大容量,直接添加到新元素到头部
public class LRUList<T> {
private static final int DEFAULT_SIZE = 10;
private int capacity;
private Node<T> head;
private Node<T> tail;
private int size;
public LRUList() {
this(DEFAULT_SIZE);
}
public LRUList(int capacity) {
this.capacity = capacity;
}
/**
* 访问元素 t
* -查询数据
* --存在 - 在头部则返回 - 不在头部,移动到头部(是否是结尾)
* --不存在 添加数据-添加到头部
* ---是否达到capacity -是 移除尾部数据 -否 ¬不移除
*
* @param t 元素
*/
public void access(T t) {
int index = indexOfElement(t);
if (index != -1) {
if (index == 0) {
return;
} else {
moveToHead(index);
}
} else {
addElement(t);
}
}
/**
* 添加元素到头部
*
* @param t 元素
*/
private void addElement(T t) {
Node<T> node = new Node<>(t);
if (size == capacity) {
removeLast();
}
Node<T> f = head;
node.prev = null;
node.next = head;
head = node;
if (f == null) {
tail = node;
} else {
f.prev = node;
}
size++;
}
/**
* 移除最后一个节点
*/
private void removeLast() {
if (isEmpty()) {
return;
}
Node<T> l = tail;
tail = tail.prev;
if (tail == null) {
head = null;
} else {
tail.next = null;
}
l.prev = null;
l.item = null;
size--;
}
/**
* 将元素移动到头部
*
* @param index 索引
*/
private void moveToHead(int index) {
Node<T> node;
if (index == size - 1) {
node = tail;
tail = tail.prev;
tail.next = null;
} else {
node = getNodeByIndex(index);
node.prev.next = node.next;
node.next.prev = node.prev;
}
node.prev = null;
node.next = head;
head.prev = node;
head = node;
}
/**
* 根据索引获取节点
*
* @param index 索引
* @return 节点
*/
private Node<T> getNodeByIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("index out of bounds");
}
Node<T> node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
/**
* 查找节点索引
*
* @param t 元素
* @return 不考虑为空的元素
*/
private int indexOfElement(T t) {
if (isEmpty()) {
return -1;
}
int index = 0;
for (Node node = head; node != null; node = node.next) {
if (node.item.equals(t)) {
return index;
}
index++;
}
return -1;
}
public boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
if (isEmpty()) {
return "[]";
}
StringBuilder builder = new StringBuilder();
for (Node<T> node = head; node != null; node = node.next) {
builder.append(node.item.toString()).append(",");
}
String result = builder.toString();
return result.substring(0, result.length() - 1);
}
private static class Node<T> {
private Node<T> prev;
private Node<T> next;
private T item;
public Node(T item) {
this.item = item;
}
}
}
测试
public class LRUListTest {
@Test
public void test() {
LRUList<Integer> frame = new LRUList<>(3);
frame.access(7);
frame.access(0);
frame.access(1);
Assert.assertEquals("1,0,7", frame.toString());
frame.access(2);
Assert.assertEquals("2,1,0", frame.toString());
frame.access(0);
Assert.assertEquals("0,2,1", frame.toString());
frame.access(0);
Assert.assertEquals("0,2,1", frame.toString());
frame.access(3);
Assert.assertEquals("3,0,2", frame.toString());
frame.access(0);
Assert.assertEquals("0,3,2", frame.toString());
frame.access(4);
Assert.assertEquals("4,0,3", frame.toString());
}
}
网友评论