LRU:Least Recently Used最近最少使用,当缓存容量不足时,先淘汰最近最少使用的数据。就像JVM垃圾回收一样,希望将存活的对象移动到内存的一端,然后清除其余空间。
缓存基本操作就是读、写、淘汰删除。
读操作时间复杂度为O(1)的那就是hash操作了,可以使用HashMap索引 key。
写操作时间复杂度为O(1),使用链表结构,在链表的一端插入节点,是可以完成O(1)操作,但是为了配合读,还要再次将节点放入HashMap中,put操作最优是O(1),最差是O(n)。
不少童鞋就有疑问了,写入时又使用map进行了put操作,为何缓存不直接使用map?没错,首先使用map存储了节点数据就是采用空间换时间,但是淘汰删除不好处理,使用map如何去记录最近最少使用(涉及到时间、频次问题)。so,使用链表可以将活跃节点移动到链表的一端,淘汰时直接从另一端进行删除。
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
public class LruCache<K,V> { /** 这里简单点直接初始化了*/ private int capacity = 2 ; private int size = 0 ; private HashMap<K,DoubleListNode<K,V>> cache = new HashMap<>(capacity); private DoubleListNode<K,V> lruNode = new DoubleListNode<K, V>( null , null , null , null ); private DoubleListNode<K,V> mruNode = new DoubleListNode<K, V>( null , null , null , null ); public V get(K key){ DoubleListNode<K,V> target = cache.get(key); if (target == null ) { return null ; } /** 使用过就移动到右侧 */ move2mru(target); return target.value; } public void put(K key,V value){ if (cache.containsKey(key)){ DoubleListNode<K,V> temp = cache.get(key); temp.value = value; /** 使用过就移动到右侧 */ move2mru(temp); return ; } /** 容量满了清除左侧 */ if (size >= capacity){ evict4lru(); } DoubleListNode<K,V> newNode = new DoubleListNode<>(mruNode, null ,key,value); if (size == 0 ){ lruNode.next = newNode; } mruNode.next = newNode; mruNode = newNode; cache.put(key,newNode); size++; } private void move2mru(DoubleListNode<K,V> newMru){ DoubleListNode<K,V> pre = newMru.pre; DoubleListNode<K,V> next = newMru.next; pre.next = next; newMru.pre = mruNode; mruNode.next = newMru; mruNode = newMru; } private void evict4lru(){ cache.remove(lruNode.next.key); lruNode.next = lruNode.next.next; size--; } public String toString(){ StringBuffer sb = new StringBuffer( "lru -> " ); DoubleListNode<K,V> temp = lruNode; while (temp!= null ){ sb.append(temp.key).append( ":" ).append(temp.value); sb.append( " -> " ); temp = temp.next; } sb.append( " -> mru " ); return sb.toString(); } public static void main(String[] args) { LruCache<String,String> cache = new LruCache<>(); cache.put( "1" , "1" ); System.out.println(cache); cache.get( "1" ); cache.put( "2" , "2" ); System.out.println(cache); cache.put( "3" , "3" ); System.out.println(cache); cache.put( "4" , "4" ); System.out.println(cache); } } class DoubleListNode<K,V>{ K key; V value; DoubleListNode<K,V> pre; DoubleListNode<K,V> next; public DoubleListNode(K key,V value){ this .key = key; this .value = value; } public DoubleListNode(DoubleListNode<K,V> pre,DoubleListNode<K,V> next,K key,V value){ this .pre = pre; this .next = next; this .key = key; this .value = value; } } |
这里使用链表,及HashMap完成了基于LRU的缓存,其中HashMap主要用来快速索引key,链表用来完成LRU机制。当然尚有许多不足,包括缓存移除remove,缓存ttl,线程安全等。
到此这篇关于浅谈Java如何实现一个基于LRU时间复杂度为O(1)的缓存的文章就介绍到这了,更多相关Java基于LRU时间复杂度为O(1)的缓存内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://blog.csdn.net/weixin_43275277/article/details/107740004