Magix.Cache介紹

轉載請註明出處:https://github.com/thx/magix/...javascript

在前端開發過程當中,咱們常常會在內存中緩存一些數據,其實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請求屢次,好比java

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

在上述代碼中仍然會發起2次對a的請求,這不是咱們討論的重點,咱們重點討論請求成功並緩存數據後,再請求該url的事情,因此這個問題略過不題git

咱們回頭看一下咱們的request方法,會發現這樣的問題:github

有些url在整個項目中或許只請求一次,咱們仍然對它的結果進行緩存,形成資源被白白佔用,若是應用在移動端,移動端的內存資源自己就比較寶貴,因此咱們不能浪費ajax

因此針對request方法中的緩存作一些改進,使它更智能些。咱們須要一種算法,保證緩存的個數不能太多,同時緩存的資源數超多時,它能聰明的刪掉那些不經常使用的緩存數據算法

那咱們看一下,當咱們要實現這樣一個算法有哪些關鍵點要考慮:api

  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;
-   var wrap={//包裝一次,方便咱們放其它信息,同時利用對象引用傳遞
-        key:key,
-        item:item
-    };
+   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){
+       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 item=cache.pop();//刪除頻率使用最小,時間最先的1個
-            delete cache[item.key];//
+           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是一個區塊管理框架,項目地址在這裏magix

區塊介紹在這裏magix區塊介紹

相關文章
相關標籤/搜索