开发者社区> 行列> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Magix中的缓存模块

简介: 在前端开发过程中,我们经常会在内存中缓存一些数据,如何根据实际需要自动缓存,清除数据是一件很有意思的事情,本篇文章来实现一个智能的缓存算法,根据使用情况对数据的智能管理
+关注继续查看

在前端开发过程中,我们经常会在内存中缓存一些数据,其实javascript的缓存比较简单,只需要声明一个变量或把一些数据挂到某个对象上即可,比如我们要实现一个对所有的ajax请求缓存的方法,简单实现如下:

var cache={};
var request=function(url,callback){
    if(cache[url]){
        callback(cache[url]);
    }else{
        $.ajax({
            url:url,
            success:function(data){
                callback(cache[url]=data);
            }
        });
    }
};

注意
示例中仅做简单演示,未考虑同时对相同的url请求多次,比如

request('/a');
request('/a');

在上述代码中仍然会发起2次对a的请求,这不是我们讨论的重点,我们重点讨论请求成功并缓存数据后,再请求该url的事情,所以这个问题略过不题

我们回头看一下我们的request方法,会发现这样的问题:

有些url在整个项目中或许只请求一次,我们仍然对它的结果进行缓存,造成资源被白白占用,如果应用在移动端,移动端的资源本身就比较宝贵,所以我们更应该珍惜资源的使用

所以针对request方法中的缓存做一些改进,使它更智能些。我们需要一种算法,保证缓存的个数不能太多,同时缓存的资源数超多时,它能聪明的删掉那些不常用的缓存数据

那我们看一下,当我们要实现这样一个算法有哪些关键点要考虑:

  1. 我们需要知道缓存中缓存了多少个资源
  2. 当我们从缓存中获取某个缓存资源时,获取的算法复杂度应该是o(1),缓存模块的作用是提高程序的效率,拿空间换时间,所以缓存模块不应该占用过多的CPU时间

明确目标后,我们就需要寻找合适的对象来缓存我们的数据:

var obj={}

根据key从obj上查找某个对象,复杂度是o(1),满足我们的第2条要求,但obj上缓存了多少个资源需要我们自已维护

var obj=[]

根据key查找某个对象时,复杂度是o(n),但数组有length,可以自动的帮我们维护当前缓存了多少个资源

我们知道数组是特殊的对象,所以我们可以把数组当成普通的对象来用。

当我们把一个缓存对象push进数组时,再根据缓存对象唯一的key,把它放到这个数组对象上

所以这时候我们第1版本的代码可能类似这样:

var Cache=function(){
    this.$cache=[];
};

Cache.prototype.set=function(key,item){
    var cache=this.$cache;
    var wrap={//包装一次,方便我们放其它信息,同时利用对象引用传递
        key:key,
        item:item
    };
    cache.push(wrap);
    cache['cache_'+key]=wrap;//加上cache_的原因是:防止key是数字或可转化为数字的字符串,这样的话就变成了如 cache['2'] 通过下标访问数组里面的元素了。
};

Cache.prototype.get=function(key){
    var res=this.$cache['cache_'+key];
    return res.item;//返回放入的资源
};

使用示例如下:

var c=new Cache();
c.set('/api/userinfo',{
    name:'彳刂'
});

console.log(c.get('/api/userinfo'));

这时候我们就完成了初步要求,知道缓存个数,查找时复杂度是o(1)

不过我们仍然需要更智能一些的缓存:

  1. 知道单个缓存资源的使用频率
  2. 知道单个缓存资源的最后使用时间
  3. 缓存中最多能放多少个缓存资源
  4. 何时清理缓存资源

我们改造下刚才的代码:

var Cache=function(max){
    this.$cache=[];
    this.$max=max | 0 ||20;
};

Cache.prototype.set=function(key,item){
    var cache=this.$cache;
    key='cache_'+key;
    var wrap=cache[key];
    if(!cache.hasOwnProperty(key){
        wrap={};
        cache.push(wrap);
        cache[key]=wrap;
    }
    wrap.item=item;
    wrap.fre=1;//初始使用频率为1
    wrap.key=key;
    wrap.time=new Date().getTime();
};

Cache.prototype.get=function(key){
    var res=this.$cache['cache_'+key];
    if(res){
        res.fre++;//更新使用频率
        res.time=new Date().getTime();
    }
    return res.item;//返回放入的资源
};

在我们第2版本的代码中,我们添加了最多缓存资源数max,同时每个缓存资源加入了使用频率fre及最后使用时间time,同时我们修改了set方法,考虑了相同key的多次set问题。

我们简单测试下:

var c=new Cache();
c.set('/api/userinfo',{
    name:'彳刂'
});

console.log(c.$cache[0].fre);//1
console.log(c.get('/api/userinfo'));
console.log(c.$cache[0].fre);//2

接下来我们要考虑一但缓存资源数超出了我们规定的max时,我们要清理掉不常用的资源。清理时我们根据频率的使用fre标志,fre最小的优先清理,同时相同的fre,我们优先清理time比较小的,这也是time设计的意义所在。

所以第3版我们的代码如下:

var Cache=function(max){
    this.$cache=[];
    this.$max=max | 0 ||20;
};

Cache.prototype.set=function(key,item){
    var cache=this.$cache;
    key='cache_'+key;
    var wrap=cache[key];
    if(!cache.hasOwnProperty(key){
        if(cache.length>=this.$max){
            cache.sort(function(a,b){
                return b.fre==a.fre?b.time-a.time:b.fre-a.fre;
            });
            var item=cache.pop();//删除频率使用最小,时间最早的1个
            delete cache[item.key];//
        }
        wrap={};
        cache.push(wrap);
        cache[key]=wrap;
    }
    wrap.item=item;
    wrap.fre=1;//初始使用频率为1
    wrap.key=key;
    wrap.time=new Date().getTime();
};

Cache.prototype.get=function(key){
    var res=this.$cache['cache_'+key];
    if(res){
        res.fre++;//更新使用频率
        res.time=new Date().getTime();
    }
    return res.item;//返回放入的资源
};

 Cache.prototype.has=funciton(key){
    return this.$cache.hasOwnProperty('cache_'+key);
 };

OK,到这里我们就完成了想要的缓存,我们结合最开始的request方法来进行实际测试:

var cache=new Cache(2);
var request=function(url,callback){
    if(cache.has(url)){
        callback(cache.get(url);
    }else{
        $.ajax({
            url:url,
            success:function(data){
                cache.set(url,data);
                callback(data);
            }
        });
    }
    
    })
};

//实际使用(假设下一个request方法被调用时,前面request的已经完成请求并缓存好了数据):
request('/api/item1');
request('/api/item2');
request('/api/item1');//命中缓存
request('/api/item3');//达到上限2,cache对象的内部$cache排序一次,删除/api/item2的缓存
request('/api/item4');//仍然达到上限2,cache对象的内部$cache排序一次,删除/api/item3的缓存

request('/api/item3');//接下来需要多次使用/api/item3,但在请求/api/item4时,它已经被删除了,所以我们需要重新请求。完成请求后,因为上限2依然满足,所以cache对象内部的$cache仍然需要排序一次,删除/api/item4
request('/api/item3');//命中缓存

根据上述使用,我们发现,一但达到缓存的上限后,带来的问题如下:

  1. 新的缓存资源进来一个,就需要重新排序一次,性能不好
  2. 有可能误删除接下来可能频率使用到的缓存资源

这时候我们就需要寻找突破。类比我们经常使用的操作系统的缓存区,我们的缓存是否也可以加入一个缓冲区呢?当整个缓存列表加上缓冲区都满的时候,才清空一次缓存区,不但能解决频繁排序的问题,也能很好的保留接下来程序中可能频繁使用到的缓存资源

来,缓存的第4版:

var Cache=function(max,buffer){
    this.$cache=[];
    this.$max=max | 0 ||20;
    this.$buffer=buffer | 0 ||5;
};

Cache.prototype.set=function(key,item){
    var cache=this.$cache;
    key='cache_'+key;
    var wrap=cache[key];
    if(!cache.hasOwnProperty(key){
        if(cache.length>=this.$max+this.$buffer){
            cache.sort(function(a,b){
                return b.fre==a.fre?b.time-a.time:b.fre-a.fre;
            });
            var buffer=this.$buffer;
            while(buffer--){
                var item=cache.pop();
                delete cache[item.key];
            }
        }
        wrap={};
        cache.push(wrap);
        cache[key]=wrap;
    }
    wrap.item=item;
    wrap.fre=1;//初始使用频率为1
    wrap.key=key;
    wrap.time=new Date().getTime();
};

Cache.prototype.get=function(key){
    var res=this.$cache['cache_'+key];
    if(res){
        res.fre++;//更新使用频率
        res.time=new Date().getTime();
    }
    return res.item;//返回放入的资源
};

Cache.prototype.has=funciton(key){
    return this.$cache.hasOwnProperty('cache_'+key);
};

这时候我们再结合request来测试一下:

var cache=new Cache(2,2);//最大2个,2个缓存区,真实可以缓存4个
var request=function(url,callback){
    if(cache.has(url)){
        callback(cache.get(url);
    }else{
        //$.ajax略
    }
};

request('/api/item1');
request('/api/item2');
request('/api/item3');//放在缓冲区
request('/api/item4');//放在缓冲区
request('/api/item5');//排序一次,清除/api/item2 /api/item1
request('/api/item6');//放在缓冲区
request('/api/item7');//放在缓冲区

至此我们就完成了比较完善的缓存模块

当然,后续我们增加缓存资源的生命期,比如20分钟后清除,也是较容易的,不在这里详解。

Magix的cache模块比这里稍微再复杂些,不过原理都是一样的。

Magix的项目地址在这里:https://github.com/thx/magix

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
14、商品分类等模块及flex布局
前言:写个分类的模块和推荐专区等模块内容,这章主要是一些页面结构的撰写,没有什么技术难点,主要是flex的布局要好好了解一下。 github:https://github.
814 0
zabbix与nagios对比
web功能:    Nagios简单直观,报警与数据都在同一页面,***、红色即为问题项。Nagios web端不要做任何配置。    Zabbix监控数据与报警是分开的,查看问题项需要看触发器,查看数据在最新数据查看。
1187 0
x3d
JMS中的消息通信模型
1. MQ简介: 消息队列(Message Queue,简称MQ),是应用程序与应用程序之间的一种通信方法。应用程序通过发送和检索出入列队的针对应用程序的数据 - 消息来通信,而无需专用连接来链接它们。
1043 0
9.5、Libgdx加速度计
(官网:www.libgdx.cn) 加速度计可以让设备通过三个坐标轴检测加速度。通过加速度可以检测设备的方向。 加速度的单位是米每秒的平方。如果一个坐标轴指向地心,加速度大概是-10米每秒的平方。
676 0
+关注
行列
作品Magix 区块管理框架:https://github.com/thx/magix 欢迎试用、star与fork
15
文章
1
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载