jive备忘--浅析jive缓存

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);
        LinkedListNode lastAccessedNode = 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 void cullCache() {
        if (size >= maxSize * .97) {
            deleteExpiredEntries();
            int desiredSize = (int)(maxSize * .90);
            while (size > desiredSize) {
                remove(lastAccessedList.getLast().object);
            }
        }
    }

 

 private final void deleteExpiredEntries() {
        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 void remove(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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值