概要
本文的想法來自於本人學習MySQL時的一個知識點:MySQL Innodb引擎中對緩衝區的處理。雖然沒有仔細研究其源碼實現,但其設計仍然啓發了我。java
本文針對LRU存在的問題,思考一種加強算法來避免或下降緩存污染,主要辦法是對原始LRU空間劃分出young與old兩段區域 ,經過命中數(或block時間)來控制,並用一個0.37的百分比係數規定old的大小。
內容分如下幾小節,實現代碼爲Java:node
1.LRU基本概念
2.LRU存在問題與LRUG設計
3.LRUG詳細說明
4.完整示例代碼算法
1.LRU基本概念
LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據。經常使用於一些緩衝區置換,頁面置換等處理。緩存
一個典型的雙向鏈表+HashMap的LRU以下:學習
2.LRU存在問題與LRUG設計this
LRU的問題是沒法迴避突發性的熱噪數據,形成緩存數據的污染。對此有些LRU的變種,如LRU-K、2Q、MQ等,經過維護兩個或多個隊列來控制緩存數據的更新淘汰。我把本文討論的算法叫LRUG,僅是我寫代碼時隨便想的一個名字。spa
LRUG使用HashMap和雙向鏈表,沒有其餘的維護隊列,而是在雙向鏈表上劃分young,old區域,young段在old段以前,有新數據時不會立刻插入到young段,而是先放入old段,若該數據持續命中,次數超過必定數量(也能夠是鎖定一段時間)後再進行插入首部的動做。兩段以37%爲界,即滿載後old段的大小最多佔總容量的37%。(圖1)
設計
(圖1)code
3.LRUG詳細說明blog
3.1首先給出雙向鏈表的節點結構,其中hitNum是命中次數:
private static class Node<K,V>{ int hitNum; K key; V value; Node<K,V> prev; Node<K,V> next; Node(K key,V value){ this.key=key; this.value=value; hitNum=0; } }
3.2在加載階段,數據以前後順序加入鏈表,半滿載時,young段已滿,新數據以插入方式加入到old段,如圖2所示。注意半滿載時,也可能有madeYoung操做,把old區的數據提到young頭。
(圖2)
public void put(K key,V value){ Node<K,V> node=caches.get(key); if(node==null){ if(caches.size()>=capcity){ caches.remove(last.key); removeLast(); } node=new Node(key,value); if(caches.size()>=pointBorder){ madeOld(node); }else{ madeYoung(node); } }else { node.value=value; if(++node.hitNum>BLOCK_HIT_NUM){ madeYoung(node); } } caches.put(key,node); }
3.3當數據命中時,若是位於young區,命中數+1後進行常規的madeYoung操做,把該項提到鏈表首部。如圖3
(圖3)
若是命中項位於old區,對命中數+1後與BLOCK_HIT_NUM設置的值作判斷,超過設定值說明該項數據可能不是突發數據,進行madeYoung操做提到鏈表首部,不然不作處理。
特別的,若是命中項正好是point,則point應該日後退一項,指向原point的下一項,此時young區膨脹了一項,而old區縮小了一項。極端狀況下,ponit項持續被命中並進行madeYoung,point不斷後退直到尾巴,此時young區佔有100%容量,而old區爲0,設置point指向last,意味着新數據項加入時,淘汰掉young區的末尾,而新數據項放在末尾成爲old區。如圖4
(圖4)
public void madeYoung(Node node){ if(first==node){ return; } if(node==point){ point=node.next; if(point==null) { point=last; } } if(node.next!=null){ node.next.prev=node.prev; } if(node.prev!=null){ node.prev.next=node.next; } if(node==last){ last=node.prev; } if(first==null||last==null){ first=last=node; point=null; return; } node.next=first; first.prev=node; first=node; } public void madeOld(Node node){ if(point.prev!=null){ point.prev.next=node; node.prev=point.prev; } if(point.next!=null){ node.next=point.next; point.next.prev=node; } point=node; }
3.4須要一個清理的方法。也能夠設置一些監測方法,如一段時間內的命中數(監測命中率)等,這與本篇主要內容無關就不寫在這了。
public void removeLast(){ if(last!=null){ if(last==point) { point=null; } last=last.prev; if(last==null) { first=null; }else{ last.next=null; } } }
4.示例代碼
主要代碼以下,時間倉促,可能一些地方會考慮不周,讀者如發現,歡迎指出。
package com.company; import java.util.HashMap; public class LRUNum<K,V> { private HashMap<K,Node> caches; private Node first; private Node last; private Node point; private int size; private int capcity; private static final int BLOCK_HIT_NUM=2; private static final float MID_POINT=0.37f; private int pointBorder; public LRUNum(int capcity){ this.size=0; this.capcity=capcity; this.caches=new HashMap<K,Node>(capcity); this.pointBorder=this.capcity-(int)(this.capcity*this.MID_POINT); } public void put(K key,V value){ Node<K,V> node=caches.get(key); if(node==null){ if(caches.size()>=capcity){ caches.remove(last.key); removeLast(); } node=new Node(key,value); if(caches.size()>=pointBorder){ madeOld(node); }else{ madeYoung(node); } }else { node.value=value; if(++node.hitNum>BLOCK_HIT_NUM){ madeYoung(node); } } caches.put(key,node); } public V get(K key){ Node<K,V> node =caches.get(key); if(node==null){ return null; } if(++node.hitNum>BLOCK_HIT_NUM){ madeYoung(node); } return node.value; } public Object remove(K key){ Node<K,V> node =caches.get(key); if(node!=null){ if(node.prev!=null){ node.prev.next=node.next; } if(node.next!=null){ node.next.prev=node.prev; } if(node==first){ first=node.next; } if(node==last){ last=node.prev; } } return caches.remove(key); } public void removeLast(){ if(last!=null){ if(last==point) { point=null; } last=last.prev; if(last==null) { first=null; }else{ last.next=null; } } } public void clear(){ first=null; last=null; point=null; caches.clear(); } public void madeYoung(Node node){ if(first==node){ return; } if(node==point){ point=node.next; if(point==null) { point=last; } } if(node.next!=null){ node.next.prev=node.prev; } if(node.prev!=null){ node.prev.next=node.next; } if(node==last){ last=node.prev; } if(first==null||last==null){ first=last=node; point=null; return; } node.next=first; first.prev=node; first=node; } public void madeOld(Node node){ if(point.prev!=null){ point.prev.next=node; node.prev=point.prev; } if(point.next!=null){ node.next=point.next; point.next.prev=node; } point=node; } private static class Node<K,V>{ int hitNum; K key; V value; Node<K,V> prev; Node<K,V> next; Node(K key,V value){ this.key=key; this.value=value; hitNum=0; } } }