游戏中缓存的设计(二)--内存缓存的实现

一般游戏中的缓存架构为:内存分布式缓存(redismemcache)。这里的内存缓存需要我们自己实现。

一,缓存需要考虑的几个问题

1,缓存的总大小

2,达到缓存最大值时的删除策略

3,缓存的过期处理,防止冗余的数据占用过多的内存。

二,详细实现

1,缓存的总大小

 因为内存的大小是有限的,所以缓存的数量必须有一最大值,当缓存达到这个最大值时,必须考虑如何转换新旧的值,即把那些旧的值删除再添加新的值。

2,达到缓存最大值的处理策略

 一般来说,我们采用LRU策略,即把“最近最少使用”的数据删除掉,释放内存。这个实现比较简单,而且易维护。

3,缓存的过期处理

如果一些缓存数据可能很长时间内不被访问,那么可以给它设定一个在内存中的有效时间,就像Redis中的Expire参数一样,当达到这个时间后,自动删除。比如,一个玩家在游戏中的登陆信息,如果他三天之内不登陆,那么他的数据在缓存中就没有任何意义了,应该把这些信息删除。实现方法有两种: 1 消极方法,即在主键被访问时判断它是否失效;积极方法,即周期性的从设置的失败时间列表中查看主键是否过期。一般这两个方法结合起来使用。

 今天我们先实现LRU。在Java中,实现LRU策略不是很麻烦,我们可以使用LinkedHashMap这个类,它有两大好处:它本身已经实现了按照访问顺序的存储,也就是说,它会把最近读取的数据放在前面,最不常读取的数据放在最后,当然它也可以按照插入顺序存储。它本身有一个方法用于判断是否需要删除最不经常读取的数据,但是,该方法默认是不需要移除的,所以,我们需要继承重写这个方法。当缓存超过最大的缓存值后,执行这个方法。大家可以去看看这个类的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


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值