LRU全称是Least Recently Used,即最近最久未使用的意思。LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。是缓存中一种常见的机制。下图展示了逻辑页面缓存的访问情况
public class LRUPageFrame {
private static class Node {
Node prev;
Node next;
int pageNum;
Node() {
}
}
private int capacity;
private int currentSize;
private Node first;// 链表头
private Node last;// 链表尾
public LRUPageFrame(int capacity) {
this.currentSize = 0;
this.capacity = capacity;
}
/**
* 获取缓存中对象
*
* @param key
* @return
*/
public void access(int pageNum) {
Node node = find(pageNum);
//在该队列中存在, 则提到队列头
if (node != null) {
moveExistingNodeToHead(node);
} else{
node = new Node();
node.pageNum = pageNum;
// 缓存容器是否已经超过大小.
if (currentSize >= capacity) {
removeLast();
}
addNewNodetoHead(node);
}
}
private void addNewNodetoHead(Node node) {
if(isEmpty()){
node.prev = null;
node.next = null;
first = node;
last = node;
} else{
node.prev = null;
node.next = first;
first.prev = node;
first = node;
}
this.currentSize ++;
}
private Node find(int data){
Node node = first;
while(node != null){
if(node.pageNum == data){
return node;
}
node = node.next;
}
return null;
}
/**
* 删除链表尾部节点 表示 删除最少使用的缓存对象
*/
private void removeLast() {
Node prev = last.prev;
prev.next = null;
last.prev = null;
last = prev;
this.currentSize --;
}
/**
* 移动到链表头,表示这个节点是最新使用过的
*
* @param node
*/
private void moveExistingNodeToHead(Node node) {
if (node == first) {
return;
}
else if(node == last){
//当前节点是链表尾, 需要放到链表头
Node prevNode = node.prev;
prevNode.next = null;
last.prev = null;
last = prevNode;
} else{
//node 在链表的中间, 把node 的前后节点连接起来
Node prevNode = node.prev;
prevNode.next = node.next;
Node nextNode = node.next;
nextNode.prev = prevNode;
}
node.prev = null;
node.next = first;
first.prev = node;
first = node;
}
private boolean isEmpty(){
return (first == null) && (last == null);
}
}
- 最简单的一种方法是利用LinkHashMap,因为它本身就有一个方法就是在所设置的缓存范围内,去除掉额外的旧数据
1 public class LRUByHashMap<K, V> {
2 /*
3 * 通过LinkHashMap简单实现LRU算法
4 */
5 /**
6 * 缓存大小
7 */
8 private int cacheSize;
9 /**
10 * 当前缓存数目
11 */
12 private int currentSize;
13
14 private LinkedHashMap<K, V> maps;
15
16 public LRUByHashMap(int cacheSize1) {
17 this.cacheSize = cacheSize1;
18
19 maps = new LinkedHashMap<K, V>() {
20 /**
21 *
22 */
23 private static final long serialVersionUID = 1;
24
25 // 这里移除旧的缓存数据
26 @Override
27 protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
28
29 // 当超过缓存数量的时候就将旧的数据移除
30 return getCurrentSize() > LRUByHashMap.this.cacheSize;
31 }
32 };
33 }
34
35 public synchronized int getCurrentSize() {
36 return maps.size();
37 }
38
39 public synchronized void put(K k, V v) {
40
41 if (k == null) {
42 throw new Error("存入的键值不能为空");
43 }
44
45 maps.put(k, v);
46 }
47
48 public synchronized void remove(K k) {
49 if (k == null) {
50 throw new Error("移除的键值不能为空");
51 }
52 maps.remove(k);
53 }
54
55 public synchronized void clear() {
56 maps = null;
57 }
58
59 // 获取集合
60 public synchronized Collection<V> getCollection() {
61
62 if (maps != null) {
63 return maps.values();
64 } else {
65 return null;
66 }
67
68 }
69
70 public static void main(String[] args) {
71
72 // 测试
73 LRUByHashMap<Integer, String> maps = new LRUByHashMap<Integer, String>(
74 3);
75 maps.put(1, "1");
76 maps.put(2, "2");
77 maps.put(3, "3");
78 maps.put(4, "4");
79 maps.put(5, "5");
80 maps.put(6, "6");
81 Collection<String> col = maps.getCollection();
82 System.out.println("存入缓存中的数据是--->>" + col.toString());
83
84 }
85 }
运行后的效果:
代码明明是put了6个Entry但最后只显示了三个,之间的三个是旧的所以直接被咔嚓掉了
- 第二种方法是利用双向链表 + HashTable
双向链表的作用是用来记录位置的,而HashTable作为容器来存储数据的
为什么用HashTable不用HashMap呢?
- HashTable的键和值都不能为null;
- 上面用LinkHashMap实现的LRU,有用到 synchronized , 让线程同步去处理,这样就避免在多线程处理统一数据时造成问题
而HashTable自带同步机制的,所以多线程就能对HashTable进行正确的处理了。
Cache的所都用有位置双连表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表 头中。这样,在多次进行Cache操作后,
最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的 Cache。当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘
汰链表最后的部分即可
1 public class LRUCache {
2
3 private int cacheSize;
4 private Hashtable<Object, Entry> nodes;//缓存容器
5 private int currentSize;
6 private Entry first;//链表头
7 private Entry last;//链表尾
8
9 public LRUCache(int i) {
10 currentSize = 0;
11 cacheSize = i;
12 nodes = new Hashtable<Object, Entry>(i);//缓存容器
13 }
14
15 /**
16 * 获取缓存中对象,并把它放在最前面
17 */
18 public Entry get(Object key) {
19 Entry node = nodes.get(key);
20 if (node != null) {
21 moveToHead(node);
22 return node;
23 } else {
24 return null;
25 }
26 }
27
28 /**
29 * 添加 entry到hashtable, 并把entry
30 */
31 public void put(Object key, Object value) {
32 //先查看hashtable是否存在该entry, 如果存在,则只更新其value
33 Entry node = nodes.get(key);
34
35 if (node == null) {
36 //缓存容器是否已经超过大小.
37 if (currentSize >= cacheSize) {
38 nodes.remove(last.key);
39 removeLast();
40 } else {
41 currentSize++;
42 }
43 node = new Entry();
44 }
45 node.value = value;
46 //将最新使用的节点放到链表头,表示最新使用的.
47 node.key = key
48 moveToHead(node);
49 nodes.put(key, node);
50 }
51
52 /**
53 * 将entry删除, 注意:删除操作只有在cache满了才会被执行
54 */
55 public void remove(Object key) {
56 Entry node = nodes.get(key);
57 //在链表中删除
58 if (node != null) {
59 if (node.prev != null) {
60 node.prev.next = node.next;
61 }
62 if (node.next != null) {
63 node.next.prev = node.prev;
64 }
65 if (last == node)
66 last = node.prev;
67 if (first == node)
68 first = node.next;
69 }
70 //在hashtable中删除
71 nodes.remove(key);
72 }
73
74 /**
75 * 删除链表尾部节点,即使用最后 使用的entry
76 */
77 private void removeLast() {
78 //链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)
79 if (last != null) {
80 if (last.prev != null)
81 last.prev.next = null;
82 else
83 first = null;
84 last = last.prev;
85 }
86 }
87
88 /**
89 * 移动到链表头,表示这个节点是最新使用过的
90 */
91 private void moveToHead(Entry node) {
92 if (node == first)
93 return;
94 if (node.prev != null)
95 node.prev.next = node.next;
96 if (node.next != null)
97 node.next.prev = node.prev;
98 if (last == node)
99 last = node.prev;
100 if (first != null) {
101 node.next = first;
102 first.prev = node;
103 }
104 first = node;
105 node.prev = null;
106 if (last == null)
107 last = first;
108 }
109 /*
110 * 清空缓存
111 */
112 public void clear() {
113 first = null;
114 last = null;
115 currentSize = 0;
116 }
117
118 }
119
120 class Entry {
121 Entry prev;//前一节点
122 Entry next;//后一节点
123 Object value;//值
124 Object key;//键
125 }