以下只是要求必须删除最近最少使用的数据的算法。实际项目中若并发量大,有性能问题。
要解决问题:
有一个有界缓存,要超过缓存容量时,优先清除最近最少使用的元素
解决思路:
用双向链表保存元素,头部存储热点元素,尾部是不太使用的元素,每次某个元素做操作时将其放入头部(原先位置元素删除)。
然而,业务场景需要根据key去查找元素,因此需要有一个map,key就是业务key,value存储了双向链接的结点(结点包含value值)。
存储一个不存在的key时,会先往链表放入一个元素(生成一个结点),然后往map中放入这个结点。
之后若对该key做set, get操作时,将元素从当前元素所在位置移到首结点。
代码如下:
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LRUCache {
/**
* 存储每个元素,靠近头部的为最近使用比较多的
*/
private LinkedList<NodeValue /* key,value object */> list;
/**
* 存储key与
*/
private Map<Integer, Node<NodeValue>> map;
private int limit;
static class NodeValue {
private Integer key;
private Integer value;
public NodeValue(Integer key, Integer value) {
this.key = key;
this.value = value;
}
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
private Lock lock = new ReentrantLock();
public LRUCache(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("capacity必须大于0");
}
list = new LinkedList<NodeValue>();
map = new ConcurrentHashMap<Integer, Node<NodeValue>>(capacity);
limit = capacity;
}
public int get(int key) {
Node<NodeValue> node = map.get(key);
if (node == null) {
return -1;
}
int value = node.item.getValue();
this.refresh(key, node);
return value;
}
/**
*
* refesh
*
* @param key
* @param oldNode
*/
private void refresh(int key, Node<NodeValue> oldNode) {
lock.lock();
if (oldNode.item.getKey().intValue() == list.first.item.getKey()) {
/**
* 当前节点是头结点就需要再进行交换了
*/
return;
}
try {
/**
* 1.删除原先结点
*/
list.removeNode(oldNode);
/**
* 2.将这个结点放入头部
*/
int value = oldNode.item.getValue();
this.addNodeToFirst(key, value);
// help gc
oldNode.next = null;
oldNode.item = null;
oldNode.prev = null;
oldNode = null;
} finally {
lock.unlock();
}
}
private void addNodeToFirst(int key, int value) {
NodeValue nodeValue = new NodeValue(key, value);
list.addFirst(nodeValue);
Node<NodeValue> freshNode = list.getNode(0);
map.put(key, freshNode);
}
private void resize() {
// 必要时进行扩容
int oldSize = map.size();
if (oldSize >= limit) {
/**
* 超过容量了,这时需要清除尾结点,空出一个位置
*/
Node<NodeValue> lastNode = list.getLastNode();
int rmKey = lastNode.item.getKey();
list.removeNode(lastNode);
map.remove(rmKey);
}
}
/**
* 设置key,value
*
* @param key
* @param value
*/
public void set(int key, int value) {
lock.lock();
try {
Node<NodeValue> oldNode = map.get(key);
if (oldNode == null) {
/**
* 必要时扩容
*/
this.resize();
/**
* 未发现有这个元素,则可以将其放入list头部,并存储key与这个node的关系到map
*/
this.addNodeToFirst(key, value);
} else {
/**
* 有了这个元素,则需要找到这个key在list中的结点,删除它并将其放入头部
*/
/**
* 1.删除原先结点
*/
list.removeNode(oldNode);
/**
* 2.将这个结点放入头部
*/
this.addNodeToFirst(key, value);
}
} finally {
lock.unlock();
}
}
static class LinkedList<E> {
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
public LinkedList() {
}
private void linkFirst(E e) {
Node<E> f = first;
Node<E> newNode = new Node<E>(null, e, f);
first = newNode;
if (f == null) {
last = newNode;
} else {
f.prev = newNode;
}
size++;
}
private E unlinkLast(Node<E> l) {
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
return element;
}
/**
* 删除一个结点
*
* @param oldNode
*/
public void removeNode(Node<E> oldNode) {
Node<E> preNode = oldNode.prev;
Node<E> nextNode = oldNode.next;
if (preNode != null) {
preNode.next = nextNode;
} else {
/**
* 当前结点是头结点,这时头结点指向要删除结点下一个
*/
first = nextNode;
}
if (nextNode != null) {
nextNode.prev = preNode;
} else {
/**
* 要删除结点是尾结点
*/
// 尾结点始终指向删除指点的上一个结点
this.last = preNode;
}
this.size = this.size - 1;
}
public Node<E> getLastNode() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l;
}
/**
* Removes and returns the last element from this list.
*
* @return the last element from this list
* @throws NoSuchElementException
* if this list is empty
*/
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
/**
* Inserts the specified element at the beginning of this list.
*
* @param e
* the element to add
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
public int size() {
return size;
}
public Node<E> getNode(int index) {
checkElementIndex(index);
return node(index);
}
/**
* Tells if the argument is the index of an existing element.
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
}
static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}