0%

LRU的实现方式

面试时被问到LRU的实现,决定尝试使用Java实现一下

一、基于LinkedHashMap

LinkedHashMap中使用了hashmap和双链表,只要重写removeEldestEntry方法,就会在超过固定大小时移除最早的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LRU {
private LinkedHashMap<Integer,Integer> cache;
private final int CAPACITY=10;
public LRU(int capacity){
cache=new LinkedHashMap<Integer, Integer>(capacity,0.75f,true){
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size()>CAPACITY;
}
};
}
public int get(int key){
return cache.getOrDefault(key,-1);
}
public void put(int key,int value){
cache.put(key,value);
}
}

其中getOrDefault方法

1
2
3
4
5
6
7
8
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return defaultValue;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}

跟踪到afterNodeAccess方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}

通过源码可以得知当accessOrder为true时,则会在访问了节点以后把当前被访问的节点放到链表的末尾

二、HashMap+双链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class LRUCachel {
class Node{
int key;
int value;
Node pre;
Node next;

public Node(int key, int value){
this.key = key;
this.value = value;
}
}
private int capacity;
HashMap<Integer,Node> map=new HashMap<>();
Node head;
Node end;
//初始化大小,缓存是有大小限制的,超过规定的大小时就得移除
public LRUCachel(int capacity) {
this.capacity = capacity;
}
//获取一个缓存数据之后,应该把这个数据在当前位置中移除,并重新添加到头的位置,这些都是在返回数据之前完成的
public int get(int key){
if(map.containsKey(key)){
Node node=map.get(key);
remove(node);
setHead(node);
return node.value;
}
return -1;
}
//移除元素分为,N的前边和N的后边都要看是怎么样的情况
public void remove(Node node){
if(node.pre!=null){
node.pre.next=node.next;
}else {
head=node.next;
}
if(node.next!=null){
node.next.pre=node.pre;
}else {
end=node.pre;
}
}
//放刚刚访问到的放到头部
public void setHead(Node node){
node.next=head;
node.pre=null;
if(head!=null){
head.pre=node;
}
head=node;
if(end==null){
end=head;
}
}
//设置看原位置是否有元素,如果有的话就替换,这证明使用过了,然后将其替换为头结点的元素,若果是一个新的节点就要判断它的大小是否符合规范
public void set(int key,int value){
if(map.containsKey(key)){
Node old =map.get(key);
old.value=value;
remove(old);
setHead(old);
}else {
Node created=new Node(key,value);
if (map.size() > capacity) {
map.remove(end.key);
remove(end);
setHead(created);
}else {
setHead(created);
}
map.put(key,created);
}
}
}