jive备忘--浅析jive缓存

来源:互联网 发布:服装绘图仪软件 编辑:程序博客网 时间:2024/05/18 22:45
jive的缓存非常简单,很适合初学者提升功力,这里将jive缓存几个关键的地方拿出来与大家分享一下。
  首先,让我们对jive缓存的类库有个框架性认识,如图:
   上图所列的,就是jive实现缓存所用的几个关键类。主要包括Cache,Cacheable,CacheObject,LinkedList以及和此类似的一整套Long的Cache类。现在把以上每个功能分别叙述一下。LinkedList,是jive自己写的一个链表,在jive缓存中所有使用到队列的地方就由它来代劳了,没有使用JDK自带的LinkedList类大概是考虑到LinkedList稍微繁琐吧;Cacheable是一个接口,其实所有工作是通过它的实现如CacheableInt,CacheableString来完成的。主要功能是封装一些类型,并使这些类型有getSizes的方法从而可以保存在缓存中;CacheObject是保存在缓存中的对象,每个CacheObject封装了一个Cacheable对象,并且提供了指向两个队列lastAccessedList和ageList节点的引用,这点在分析Cache时会知道什么作用。
   OK,下面是重头戏Cache,整个缓存重要的功能就是由它搞定的。
   
  首先,它提供了一个HashMap作为整个缓存的空间,该HashMap中每个元素是一个CacheObject。然而,这个HashMap在内存中不可能无限大,因此必须伴随着一套管理它的方法,在缓存不够时将Cache换出或者覆盖,比如我们可以使用LRU,FIFO等等方法(可以参考操作系统和计算机组成原理),jive很简单的采用了两个队列(精确点说还是链表)完成了这套管理,分别是lastAccessedList和ageList。其中LastAccessedList按照访问的频繁程度排序,最频繁的在最前,这样每次换出时就可以选取队尾的对象;而ageList则将对象按照进入的时间排序,最早的在最前。
  Cache有几个主要的方法:add,remove,get,cullCache
  每次有新对象需要缓存时先做两个检查:一,元素是否已经在其中;二,元素的大小是否已经超过整个缓存的90%,之后就将元素入Cache,同时lastAccessedList和ageList头加入该元素的key,当然,ageList还要记录入Cache时间,之后调用cullCache(),从名字就看得出是对Cache进行删减的。以下就是add方法
 

public synchronized void add(Object key,Cacheable object) {
       remove(key);

       int objectSize = object.getSize();
       if (objectSize > maxSize * .90) {
           return;
       }
       size += objectSize;
       CacheObject cacheObject = new CacheObject(object,objectSize);
       cachedObjectsHash.put(key, cacheObject);
        LinkedListNodelastAccessedNode = lastAccessedList.addFirst(key);
        cacheObject.lastAccessedListNode= lastAccessedNode;
       LinkedListNode ageNode = ageList.addFirst(key);
       ageNode.timestamp = System.currentTimeMillis();
       cacheObject.ageListNode = ageNode;

       cullCache();
    }

 再来看cullCache方法,首先判断当前的Cache大小大于所规定的最大Size,这时我们才进行删除,删除的策略是,先删除过期节点,如果完了Size还大于最大Size的90%,接着再删去最不频繁访问的节点,你可能会问为什么要留10%出来,如果每次都不留稍多点的空间,那么下次可能很快又要删,造成不必要的时间浪费,此时宁可牺牲可能接下来就被访问的而节约的时间,也不要把时间浪费在频繁的维护节点上。仔细推敲这个删除策略,就会发现,首先删除时用的是ageList,第二次删除时用的是lastAccessedList。现在你知道为什么需要设两个队列了,因为可以满足不同的需要,第一种是以时间来评定是否删除,第二种是以访问的频繁程度来评定的。如果我们还有第三种删除方式,现在你一定知道该怎么做了。
  deleteExpiredEntries方法主要用来删除过期节点,从最后一个开始向前判断每个节点进入Cache的时间是否过期,如果过期了当然是删之而后快。这里有个问题需要注意:为什么要从后向前而不是从前向后?对了,因为add时就是最老的在最后,因为入Cache都加入到对头。

 private final voidcullCache() {
       if (size >= maxSize * .97) {
           deleteExpiredEntries();
           int desiredSize = (int)(maxSize * .90);
           while (size > desiredSize) {
               remove(lastAccessedList.getLast().object);
           }
       }
    }

 

 private final voiddeleteExpiredEntries() {
       if (maxLifetime <= 0) {
           return;
       }
         
       LinkedListNode node = ageList.getLast();
        if (node == null) {
           return;
       }

       long expireTime = currentTime - maxLifetime;

       while(expireTime > node.timestamp) {
           remove(node.object);

           node = ageList.getLast();
           if (node == null) {
               return;
           }
       }
    }

 

  删除操作也是被频繁调用的重点操作,主要就是将对象从Cache中删除,从两个队列中也相应的删除,当然,最重要的别忘了,将Cache当前的Size修正
 public synchronized voidremove(Object key) {
       CacheObject cacheObject =(CacheObject)cachedObjectsHash.get(key);
       if (cacheObject == null) {
           return;
       }
       cachedObjectsHash.remove(key);
       cacheObject.lastAccessedListNode.remove();
       cacheObject.ageListNode.remove();
       cacheObject.ageListNode = null;
       cacheObject.lastAccessedListNode = null;
       size -= cacheObject.size;
   }
  在get方法中,首先删除过期那些节点,之后对Cache的命中率或失败率进行修正,值得注意的是找到之后对lastAccessedList队列的处理,找到之后就将元素移到对头,从此可以看出来,lastAccessedList队列的目的就是要保持最近经常访问的元素在前从而不会被删除。
 

public synchronized Cacheable get(Object key){
       deleteExpiredEntries();

       CacheObject cacheObject =(CacheObject)cachedObjectsHash.get(key);
       if (cacheObject == null) {
            cacheMisses++;
           return null;
       }

       cacheHits++;

       cacheObject.lastAccessedListNode.remove();
       lastAccessedList.addFirst(cacheObject.lastAccessedListNode);

       return cacheObject.object;
    }

 
  对jive的缓存已经大体完了,看起来挺简单吧,其实思想的确不难。让我们再来用例子回顾一下。比如现在新建了一个Cache,来了一个字符串"levi",它经过CacheableString和CacheObject包装后,进入Cache,此时它在lastAccessedList和ageList的第一位,接下来又来一个字符串"pig",此时它也进Cache,排lastAccessedList和ageList第一位,"levi"不幸成了第二,这时程序访问到"levi",于是程序修改lastAccessedList,它改变了排名成了第一,但ageList中仍然是第二,这时又来一个"snake",假定Cache已满,就会淘汰,假定几个对象都没过期,那这时淘汰的就是"fox"了。仔细推敲,其实现非常有意思。
  另外,你一定注意到size是用字节表示的,你一定会问jive是如何计算对象大小的,这里可以去参考CacheSizes,具体做法是采用一个估算值,比如Int型size就为4,请注意,这里是固定死在程序的,只有java这种类型大小固定的语言才能这样写,否则就要sizeof了,哈哈。


转自 http://blog.sina.com.cn/s/blog_58adc9e70100097u.html

原创粉丝点击