一般游戏中的缓存架构为:内存—分布式缓存(redis,memcache)。这里的内存缓存需要我们自己实现。
一,缓存需要考虑的几个问题
1,缓存的总大小
2,达到缓存最大值时的删除策略
3,缓存的过期处理,防止冗余的数据占用过多的内存。
二,详细实现
1,缓存的总大小
因为内存的大小是有限的,所以缓存的数量必须有一最大值,当缓存达到这个最大值时,必须考虑如何转换新旧的值,即把那些旧的值删除再添加新的值。
2,达到缓存最大值的处理策略
一般来说,我们采用LRU策略,即把“最近最少使用”的数据删除掉,释放内存。这个实现比较简单,而且易维护。
3,缓存的过期处理
如果一些缓存数据可能很长时间内不被访问,那么可以给它设定一个在内存中的有效时间,就像Redis中的Expire参数一样,当达到这个时间后,自动删除。比如,一个玩家在游戏中的登陆信息,如果他三天之内不登陆,那么他的数据在缓存中就没有任何意义了,应该把这些信息删除。实现方法有两种: 1 消极方法,即在主键被访问时判断它是否失效;2 积极方法,即周期性的从设置的失败时间列表中查看主键是否过期。一般这两个方法结合起来使用。
今天我们先实现LRU。在Java中,实现LRU策略不是很麻烦,我们可以使用LinkedHashMap这个类,它有两大好处:1 它本身已经实现了按照访问顺序的存储,也就是说,它会把最近读取的数据放在前面,最不常读取的数据放在最后,当然它也可以按照插入顺序存储。2 它本身有一个方法用于判断是否需要删除最不经常读取的数据,但是,该方法默认是不需要移除的,所以,我们需要继承重写这个方法。当缓存超过最大的缓存值后,执行这个方法。大家可以去看看这个类的API。
考虑到多线程下并发的情况,我们使用Collections.synchronizedMap();做一层包装,进行数据同步处理。
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
|
package
com.wgs.cache.manager;
import
java.util.Collections;
import
java.util.LinkedHashMap;
import
java.util.Map;
import
java.util.Set;
public
class
CacheManager {
private
final
Map<String, Object> cacheMap;
//加载因子
private
float
loadFactor =
0
.75f;
public
CacheManager(
final
int
cacheSize){
//这里为true,表示按访问顺序存储,最常访问的数据会放在前面
cacheMap = Collections.synchronizedMap(
new
LinkedHashMap<String, Object>(cacheSize, loadFactor,
true
){
private
static
final
long
serialVersionUID = -7587080022442225813L;
/**
* 重写删除策略方法,当缓存容量大于给定的大小时,开始移除最不常访问的数据
*/
@Override
protected
boolean
removeEldestEntry(Map.Entry<String, Object> eldest) {
if
(cacheMap.size() > cacheSize){
return
true
;
}
return
false
;
}
});
}
/**
* 向缓存中添加数据
* @param key
* @param value
*/
public
void
put(String key,Object value){
if
(key ==
null
){
return
;
}
cacheMap.put(key, value);
}
public
Object get(String key){
if
(key ==
null
){
return
null
;
}
Object obj = cacheMap.get(key);
if
(obj ==
null
){
return
null
;
}
return
obj;
}
public
void
remove(String key){
cacheMap.remove(key);
}
public
Set<Map.Entry<String, Object>> getAll(){
return
cacheMap.entrySet();
}
public
static
void
main(String[] args) {
CacheManager cacheManager =
new
CacheManager(
3
);
cacheManager.put(
"a"
,
"aaa"
);
cacheManager.put(
"b"
,
"bbb"
);
cacheManager.put(
"c"
,
"ccc"
);
cacheManager.get(
"a"
);
cacheManager.get(
"c"
);
cacheManager.put(
"d"
,
"dddd"
);
Set<Map.Entry<String, Object>> setMap = cacheManager.getAll();
for
(Map.Entry<String, Object> entry : setMap){
System.out.println(entry.getValue());
}
}
}
|
我们运行上面的代码:
1
2
3
|
aaa
ccc
dddd
|
可以看到,我们把总大小定义为3,先加入三个数据,然后对a,c进行一次取用,再添加新的值,遍历出之后,那个没有被使用的b的值被自动删除了。
在实际应用中,代码的封装要比这复杂的多,调用起来也方便的多。这只是一个思路。根据你的实际应用可以自己封装。
原文:http://www.youxijishu.com/blogs/33.html