
提到緩存,不得不提就是緩存算法(淘汰算法),常見算法有LRU、LFU和FIFO等算法,每種算法各有各的優勢和缺點及適應環境。
1、LRU(Least Recently Used ,最近最少使用)
算法根據數據的最近訪問記錄來淘汰數據,其原理是如果數據最近被訪問過,將來被訪問的幾概率相對比較高,最常見的實現是使用一個鏈表保存緩存數據,詳細具體算法如下:
1. 新數據插入到鏈表頭部;
2. 每當緩存數據命中,則將數據移到鏈表頭部;
3. 當鏈表滿的時候,將鏈表尾部的數據丟棄;
2、LFU(Least Frequently Used,最不經常使用)
算法根據數據的歷史訪問頻率來淘汰數據,其原理是如果數據過去被訪問次數越多,將來被訪問的幾概率相對比較高。LFU的每個數據塊都有一個引用計數,所有數據塊按照引用計數排序,具有相同引用計數的數據塊則按照時間排序。
具體算法如下:
1. 新加入數據插入到隊列尾部(因為引用計數為1);
2. 隊列中的數據被訪問后,引用計數增加,隊列重新排序;
3. 當需要淘汰數據時,將已經排序的列表最后的數據塊刪除;
3、FIFO(First In First Out ,先進先出)
算法是根據先進先出原理來淘汰數據的,實現上是最簡單的一種,具體算法如下:
1. 新訪問的數據插入FIFO隊列尾部,數據在FIFO隊列中順序移動;
2. 淘汰FIFO隊列頭部的數據;
評價一個緩存算法好壞的標准主要有兩個,一是命中率要高,二是算法要容易實現。當存在熱點數據時,LRU的效率很好,但偶發性的、周期性的批量操作會導致LRU命中率急劇下降,緩存污染情況比較嚴重。LFU效率要優於LRU,且能夠避免周期性或者偶發性的操作導致緩存命中率下降的問題。但LFU需要記錄數據的歷史訪問記錄,一旦數據訪問模式改變,LFU需要更長時間來適用新的訪問模式,即:LFU存在歷史數據影響將來數據的“緩存污染”效用。FIFO雖然實現很簡單,但是命中率很低,實際上也很少使用這種算法。
基於現有jdk類庫,我們可以很容易實現上面的緩存算法
首先定義緩存接口類
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
|
/**
* 緩存接口
* @author Wen
*
*/
public
interface
Cache<K,V> {
/**
* 返回當前緩存的大小
*
* @return
*/
int
size();
/**
* 返回默認存活時間
*
* @return
*/
long
getDefaultExpire();
/**
* 向緩存添加value對象,其在緩存中生存時間為默認值
*
* @param key
* @param value
*/
void
put(K key ,V value) ;
/**
* 向緩存添加value對象,並指定存活時間
* @param key
* @param value
* @param expire 過期時間
*/
void
put(K key ,V value ,
long
expire ) ;
/**
* 查找緩存對象
* @param key
* @return
*/
V get(K key);
/**
* 淘汰對象
*
* @return 被刪除對象大小
*/
int
eliminate();
/**
* 緩存是否已經滿
* @return
*/
boolean
isFull();
/**
* 刪除緩存對象
*
* @param key
*/
void
remove(K key);
/**
* 清除所有緩存對象
*/
void
clear();
/**
* 返回緩存大小
*
* @return
*/
int
getCacheSize();
/**
* 緩存中是否為空
*/
boolean
isEmpty();
}
|
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
import
java.util.Map;
import
java.util.concurrent.locks.Lock;
import
java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 默認實現
*/
public
abstract
class
AbstractCacheMap<K,V>
implements
Cache<K,V> {
class
CacheObject<K2,V2> {
CacheObject(K2 key, V2 value,
long
ttl) {
this
.key = key;
this
.cachedObject = value;
this
.ttl = ttl;
this
.lastAccess = System.currentTimeMillis();
}
final
K2 key;
final
V2 cachedObject;
long
lastAccess;
// 最后訪問時間
long
accessCount;
// 訪問次數
long
ttl;
// 對象存活時間(time-to-live)
boolean
isExpired() {
if
(ttl ==
0
) {
return
false
;
}
return
lastAccess + ttl < System.currentTimeMillis();
}
V2 getObject() {
lastAccess = System.currentTimeMillis();
accessCount++;
return
cachedObject;
}
}
protected
Map<K,CacheObject<K,V>> cacheMap;
private
final
ReentrantReadWriteLock cacheLock =
new
ReentrantReadWriteLock();
private
final
Lock readLock = cacheLock.readLock();
private
final
Lock writeLock = cacheLock.writeLock();
protected
int
cacheSize;
// 緩存大小 , 0 -> 無限制
protected
boolean
existCustomExpire ;
//是否設置默認過期時間
public
int
getCacheSize() {
return
cacheSize;
}
protected
long
defaultExpire;
// 默認過期時間, 0 -> 永不過期
public
AbstractCacheMap(
int
cacheSize ,
long
defaultExpire){
this
.cacheSize = cacheSize ;
this
.defaultExpire = defaultExpire ;
}
public
long
getDefaultExpire() {
return
defaultExpire;
}
protected
boolean
isNeedClearExpiredObject(){
return
defaultExpire >
0
|| existCustomExpire ;
}
public
void
put(K key, V value) {
put(key, value, defaultExpire );
}
public
void
put(K key, V value,
long
expire) {
writeLock.lock();
try
{
CacheObject<K,V> co =
new
CacheObject<K,V>(key, value, expire);
if
(expire !=
0
) {
existCustomExpire =
true
;
}
if
(isFull()) {
eliminate() ;
}
cacheMap.put(key, co);
}
finally
{
writeLock.unlock();
}
}
/**
* {@inheritDoc}
*/
public
V get(K key) {
readLock.lock();
try
{
CacheObject<K,V> co = cacheMap.get(key);
if
(co ==
null
) {
return
null
;
}
if
(co.isExpired() ==
true
) {
cacheMap.remove(key);
return
null
;
}
return
co.getObject();
}
finally
{
readLock.unlock();
}
}
public
final
int
eliminate() {
writeLock.lock();
try
{
return
eliminateCache();
}
finally
{
writeLock.unlock();
}
}
/**
* 淘汰對象具體實現
*
* @return
*/
protected
abstract
int
eliminateCache();
public
boolean
isFull() {
if
(cacheSize ==
0
) {
//o -> 無限制
return
false
;
}
return
cacheMap.size() >= cacheSize;
}
public
void
remove(K key) {
writeLock.lock();
try
{
cacheMap.remove(key);
}
finally
{
writeLock.unlock();
}
}
public
void
clear() {
writeLock.lock();
try
{
cacheMap.clear();
}
finally
{
writeLock.unlock();
}
}
public
int
size() {
return
cacheMap.size();
}
public
boolean
isEmpty() {
return
size() ==
0
;
}
}
|
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
|
import
java.util.Iterator;
import
java.util.LinkedHashMap;
import
java.util.Map;
/**
* LRU 實現
* @author Wen
*
* @param <K>
* @param <V>
*/
public
class
LRUCache<K, V>
extends
AbstractCacheMap<K, V> {
public
LRUCache(
int
cacheSize,
long
defaultExpire) {
super
(cacheSize , defaultExpire) ;
//linkedHash已經實現LRU算法 是通過雙向鏈表來實現,當某個位置被命中,通過調整鏈表的指向將該位置調整到頭位置,新加入的內容直接放在鏈表頭,如此一來,最近被命中的內容就向鏈表頭移動,需要替換時,鏈表最后的位置就是最近最少使用的位置
this
.cacheMap =
new
LinkedHashMap<K, CacheObject<K, V>>( cacheSize +
1
, 1f,
true
) {
@Override
protected
boolean
removeEldestEntry(
Map.Entry<K, CacheObject<K, V>> eldest) {
return
LRUCache.
this
.removeEldestEntry(eldest);
}
};
}
private
boolean
removeEldestEntry(Map.Entry<K, CacheObject<K, V>> eldest) {
if
(cacheSize ==
0
)
return
false
;
return
size() > cacheSize;
}
/**
* 只需要實現清除過期對象就可以了,linkedHashMap已經實現LRU
*/
@Override
protected
int
eliminateCache() {
if
(!isNeedClearExpiredObject()){
return
0
;}
Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator();
int
count =
0
;
while
(iterator.hasNext()){
CacheObject<K, V> cacheObject = iterator.next();
if
(cacheObject.isExpired() ){
iterator.remove();
count++ ;
}
}
return
count;
}
}
|
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
|
import
java.util.HashMap;
import
java.util.Iterator;
//LFU實現
public
class
LFUCache<K,V>
extends
AbstractCacheMap<K, V> {
public
LFUCache(
int
cacheSize,
long
defaultExpire) {
super
(cacheSize, defaultExpire);
cacheMap =
new
HashMap<K, CacheObject<K,V>>(cacheSize+
1
) ;
}
/**
* 實現刪除過期對象 和 刪除訪問次數最少的對象
*
*/
@Override
protected
int
eliminateCache() {
Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator();
int
count =
0
;
long
minAccessCount = Long.MAX_VALUE ;
while
(iterator.hasNext()){
CacheObject<K, V> cacheObject = iterator.next();
if
(cacheObject.isExpired() ){
iterator.remove();
count++ ;
continue
;
}
else
{
minAccessCount = Math.min(cacheObject.accessCount , minAccessCount) ;
}
}
if
(count >
0
)
return
count ;
if
(minAccessCount != Long.MAX_VALUE ){
iterator = cacheMap.values().iterator();
while
(iterator.hasNext()){
CacheObject<K, V> cacheObject = iterator.next();
cacheObject.accessCount -= minAccessCount ;
if
(cacheObject.accessCount <=
0
){
iterator.remove();
count++ ;
}
}
}
return
count;
}
}
|
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
|
import
java.util.Iterator;
import
java.util.LinkedHashMap;
/**
* FIFO實現
* @author Wen
*
* @param <K>
* @param <V>
*/
public
class
FIFOCache<K, V>
extends
AbstractCacheMap<K, V> {
public
FIFOCache(
int
cacheSize,
long
defaultExpire) {
super
(cacheSize, defaultExpire);
cacheMap =
new
LinkedHashMap<K, CacheObject<K, V>>(cacheSize +
1
);
}
@Override
protected
int
eliminateCache() {
int
count =
0
;
K firstKey =
null
;
Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator();
while
(iterator.hasNext()) {
CacheObject<K, V> cacheObject = iterator.next();
if
(cacheObject.isExpired()) {
iterator.remove();
count++;
}
else
{
if
(firstKey ==
null
)
firstKey = cacheObject.key;
}
}
if
(firstKey !=
null
&& isFull()) {
//刪除過期對象還是滿,繼續刪除鏈表第一個
cacheMap.remove(firstKey);
}
return
count;
}
}
|