本文编写于 1954 天前,最后修改于 1505 天前,其中某些信息可能已经过时。
介绍
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
实现
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
public class LRUCache<K, V> {
/**
* 节点类
*/
class Node {
/** 父节点 */
Node parent;
/** 子节点 */
Node child;
/** 散列表的键 */
K key;
/** 散列表的值 */
V value;
/**
* 节点类的构造方法
* @param key 散列表的键
* @param value 散列表的值
*/
private Node(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"key=" + key +
", value=" + value +
'}';
}
}
/** 缓存的最大容量 */
private final int capacity;
/** 当前缓存的实际大小 */
private int size;
/** 头节点 */
private Node root;
/** 尾节点 */
private Node tail;
/**
* LRU 缓存对象的构造方法
* @param capacity 缓存的最大容量
*/
public LRUCache(int capacity) {
this.capacity = capacity;
}
/**
* 根据散列表的键从缓存中取出散列表中对应的值
* @param key 散列表的键
* @return 散列表中对应的值
*/
public V get(K key) {
Node node = searchNode(key);
if (node != null) {
V value = node.value;
//如果root节点是查找节点则无需提升节点
if (node == root) {
return value;
}
//进行节点提升
setHeaderNode(node);
return value;
}
return null;
}
/**
* 向缓存中放入一组缓存键值
* @param key 散列表的键
* @param value 散列表的值
*/
public void put(K key, V value) {
if (root == null) {
root = new Node(key, value);
tail = root;
size++;
return;
}
if (get(key) != null) {
root.value = value;
return;
}
if (size < capacity) {
size++;
} else {
//移除队尾节点
//如果是尾节点是根节点
if (tail == root) {
root = null;
tail = null;
} else {
//有且仅有根尾两个节点
if (root.child == tail && tail.parent == root) {
root.child = null;
tail.parent = null;
tail = root;
} else {
Node tempNode = tail.parent;
tail.parent = null;
tempNode.child = null;
tail = tempNode;
}
}
}
Node node = new Node(key, value);
node.child = root;
if (root != null) {
root.parent = node;
}
root = node;
if (tail == null) {
tail = root;
}
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("size=");
stringBuilder.append(size).append(",data=[");
for (Node node = root; node != null; node = node.child) {
stringBuilder.append(node.key).append("=").append(node.value).append(",");
}
if (stringBuilder.charAt(stringBuilder.length() - 1) == ',') {
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
}
stringBuilder.append("]");
return stringBuilder.toString();
}
/**
* 根据key进行节点搜索
* @param key key
* @return 搜索到的节点
*/
private Node searchNode(K key) {
if (size < 1) {
return null;
} else if (size == 1) {
return root.key.equals(key) ? root : null;
} else if (size == 2) {
return root.key.equals(key) ? root : tail.key.equals(key) ? tail : null;
}
Node left = root, right = tail;
while (left != right && left.child != right && right.parent != left) {
if (left.key.equals(key)) {
return left;
}
if (right.key.equals(key)) {
return right;
}
left = left.child;
right = right.parent;
}
return left.key.equals(key) ? left : right.key.equals(key) ? right : null;
}
/**
* 将指定节点提升到根节点
* @param target 指定的节点
*/
private void setHeaderNode(Node target) {
Node source = root;
//只有两个节点
if (size == 2) {
source.parent = target;
target.child = source;
source.child = null;
target.parent = null;
root = target;
tail = source;
} else if (target == tail) {
//尾节点需要提升到根节点
target.parent.child = null;
tail = target.parent;
target.parent = null;
target.child = source;
source.parent = target;
root = target;
} else {
//中间节点需要提升到根节点
Node tempNode1 = target.child;
Node tempNode2 = target.parent;
tempNode1.parent = tempNode2;
tempNode2.child = tempNode1;
target.child = source;
source.parent = target;
target.parent = null;
root = target;
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
LRUCache<Integer, Integer> cache = new LRUCache<>(2048);
Random random = new Random();
for (int i = 0;i < 5000;i++) {
int value = random.nextInt(1000);
if (random.nextBoolean()) {
cache.put(value, value);
} else {
System.out.println(cache.get(value));
}
}
System.out.printf("running time: %dms", System.currentTimeMillis() - start);
}