Java实现LRU算法(线程安全)
LRU原理
LRU缓存的思想:
1、固定缓存大小,需要给缓存分配一个固定的大小。
2、每次读取缓存都会改变缓存的使用时间,将缓存的存在时间重新刷新。
3、需要在缓存满了后,将最近最久未使用的缓存删除,再添加最新的缓存。
算法实现的思路
使用HashMap和链表来实现,链表用于存储我们缓存的节点,Map存储所有的节点,当需要淘汰一个节点的时候,只需要删除链表的尾部节点同时删除Map中的数据。
1、新数据插入到链表头部;
2、每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3、 当链表满的时候,将链表尾部的数据丢弃。
若干疑问解答
1、有了链表为什么还需压Map?
空间换时间的思想。
第一:我们在get缓存的时候从Map中获取的时候基本上时间复杂度控制在O(1),如果从链表中一次遍历的话时间复杂度是O(n)。
第二:我们访问一个已经存在的节点时候,需要将这个节点移动到header节点后,这个时候需要在链表中删除这个节点,并重新在header后面新增一个节点。这个时候先去Map中获取这个节点删除节点关系,避免了从链表中遍历,将时间复杂度从O(N)减少为)O(1)
2、为什么需要header和tailer节点
根据我们前面的设计,所有被访问的节点都需要放在链表最前面,缓存淘汰的时候删除最后的节点,header节点方便新增节点(我们采用头插法增加节点),tailer节点方便缓存淘汰,避免遍历从头到尾遍历
代码
代码片
.
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author rewnei2
* @version v0.1 2019/4/15 17:29
*/
public class LRU<K, V> {
private ReadWriteLock lock = new ReentrantReadWriteLock();
private int cacheSize;
private Map<K, Node<K>> caches;
/**
* 节点的大小
*/
private int size;
/**
* 头节点
*/
private Node<K> header;
/**
* 尾部节点
*/
private Node<K> tailer;
public LRU(int cacheSize) {
this.cacheSize = cacheSize;
this.caches = new HashMap<>();
header = new Node<K>(null, null, null);
tailer = new Node<K>(null, header, null);
header.next = tailer;
}
public void put(K key) {
lock.writeLock().lock();
try {
Node<K> node = caches.get(key);
if (node == null) {
if (caches.size() >= cacheSize) {
deleteTail();
}
addToHead(key);
} else {
//删除已经存在的节点数据
deleteNode(node);
addToHead(node.data);
}
} finally {
lock.writeLock().unlock();
}
}
public K get(K key) {
lock.writeLock().lock();
try {
Node<K> node = caches.get(key);
if (node == null) {
return null;
}
deleteNode(node);
addToHead(key);
return key;
} finally {
lock.writeLock().unlock();
}
}
static class Node<K> {
/**
*
*/
public Node<K> pre;
public Node<K> next;
public K data;
public Node(K data, Node<K> pre, Node<K> next) {
this.data = data;
this.pre = pre;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
'}';
}
}
/**
* 头尾保持两个空的header和tail节点,头插法增加节点并且增加cache中数据
*
* @param key
*/
public Node<K> addToHead(K key) {
Node<K> node = new Node<>(key, null, null);
Node<K> nextNode = header.next;
node.next = nextNode;
nextNode.pre = node;
node.pre = header;
header.next = node;
size++;
caches.put(key, node);
return node;
}
/**
* 删除链表的中间节点的节点信息
*
* @param target
*/
public void deleteNode(Node<K> target) {
Node<K> preNode = target.pre;
Node<K> nextNode = target.next;
preNode.next = nextNode;
nextNode.pre = preNode;
target.pre = null;
target.next = null;
size--;
}
/**
* 删除尾部节点同时删除缓存中的数据
*/
public void deleteTail() {
Node lastNode = tailer.pre;
if (lastNode == header) {
return;
}
Node<K> preNode = lastNode.pre;
preNode.next = tailer;
tailer.pre = preNode;
lastNode.pre = null;
lastNode.next = null;
size--;
caches.remove(lastNode.data);
}
public void print() {
lock.readLock().lock();
try {
Node<K> current = header.next;
StringBuffer sb = new StringBuffer();
while (current != tailer) {
sb.append(current.data + " ");
current = current.next;
}
System.out.println("size=" + size + ";cache size="+caches.size()+"当前的链表数据有:" + sb.toString());
} finally {
lock.readLock().unlock();
}
}
public static void main(String[] args) {
LRU<String, String> lru = new LRU<>(4);
//DoubleLink<String> doubleLink = new DoubleLink<>();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
lru.put("1");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
lru.put("2");
lru.print();
lru.put("3");
lru.print();
lru.get("2");
lru.put("4");
//System.out.println(doubleLink.size);
lru.print();
lru.put("5");
lru.print();
lru.put("6");
lru.print();
lru.put("7");
lru.put("8");
lru.put("9");
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lru.put("11");
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
lru.put("12");
lru.get("12");
lru.put("13");
lru.get("13");
lru.print();
lru.put("14");
//System.out.println(doubleLink.size);
lru.print();
lru.put("15");
lru.print();
lru.put("16");
lru.print();
lru.put("17");
lru.put("18");
lru.put("19");
}
});
thread.start();
//thread1.start();
}
}
后记
由于本人水平有限,本文难免有不足的地方,请各位批评指正。