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方法中的快取做一些改進,使它更智慧些。我們需要一種演算法,保證快取的個數不能太多,同時快取的資源數超多時,它能聰明的刪掉那些不常用的快取資料
那我們看一下,當我們要實現這樣一個演算法有哪些關鍵點要考慮:
- 我們需要知道快取中快取了多少個資源
- 當我們從快取中獲取某個快取資源時,獲取的演算法複雜度應該是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)
不過我們仍然需要更智慧一些的快取:
- 知道單個快取資源的使用頻率
- 知道單個快取資源的最後使用時間
- 快取中最多能放多少個快取資源
- 何時清理快取資源
我們改造下剛才的程式碼:
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');//命中快取
根據上述使用,我們發現,一但達到快取的上限後,帶來的問題如下:
- 新的快取資源進來一個,就需要重新排序一次,效能不好
- 有可能誤刪除接下來可能頻率使用到的快取資源
這時候我們就需要尋找突破。類比我們經常使用的作業系統的快取區,我們的快取是否也可以加入一個緩衝區呢?當整個快取列表加上緩衝區都滿的時候,才清空一次快取區,不但能解決頻繁排序的問題,也能很好的保留接下來程式中可能頻繁使用到的快取資源
來,快取的第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
相關文章
- Masa Framework原始碼解讀-02快取模組(分散式快取進階之多級快取)Framework原始碼快取分散式
- HTTP 快取中的 VaryHTTP快取
- Glide中的快取IDE快取
- 前端魔法堂:手寫快取模組前端快取
- 修改Ehcache快取中取到的值,快取中的值也被修改了快取
- mybaits原始碼分析--快取模組(六)AI原始碼快取
- node中的快取機制快取
- MyBatis中的一級快取和二級快取介紹MyBatis快取
- 深入理解Android中的快取機制(三)磁碟快取Android快取
- 程式設計中快取的使用程式設計快取
- C#中普通快取的使用C#快取
- ASP.NET Core 中的快取ASP.NET快取
- 快取使用中的注意事項快取
- 清除 Electron 中的快取資料快取
- 深入理解Android中的快取機制(一)快取簡介Android快取
- Redis的快取穿透、快取雪崩、快取擊穿的區別Redis快取穿透
- macOS 中清除 DNS 快取MacDNS快取
- 快取穿透、快取擊穿、快取雪崩、快取預熱快取穿透
- java的Integer中也會有快取Java快取
- Hibernate中的Session快取問題Session快取
- C#呼叫Couchbase中的Memcached快取C#快取
- 從CPU快取看快取的套路快取
- 快取穿透、快取擊穿、快取雪崩快取穿透
- 快取穿透、快取雪崩、快取擊穿快取穿透
- Redis快取擊穿、快取穿透、快取雪崩Redis快取穿透
- HTTP快取——協商快取(快取驗證)HTTP快取
- [Redis]快取穿透/快取擊穿/快取雪崩Redis快取穿透
- 對於前端快取的理解(快取機制和快取型別)前端快取型別
- 瀏覽器的快取機制—強快取與協商快取瀏覽器快取
- 快取穿透 快取雪崩快取穿透
- 【構建Android快取模組】(二)Memory Cache & File CacheAndroid快取
- jQuery1.9.1原始碼分析--資料快取Data模組jQuery原始碼快取
- vue中methods中的方法閉包快取問題Vue快取
- 快取問題(一) 快取穿透、快取雪崩、快取併發 核心概念快取穿透
- [記]SAF 中快取服務的實現快取
- Android中清楚Cookie和WebView的快取AndroidCookieWebView快取
- Android 中 EventBus 的使用(2):快取事件Android快取事件
- Web應用中快取的七種武器Web快取