1. 程式人生 > >LRU演算法四種實現方式介紹

LRU演算法四種實現方式介紹

實現LRU

1.用一個數組來儲存資料,給每一個數據項標記一個訪問時間戳,每次插入新資料項的時候,先把陣列中存在的資料項的時間戳自增,並將新資料項的時間戳置為0並插入到陣列中。每次訪問陣列中的資料項的時候,將被訪問的資料項的時間戳置為0。當陣列空間已滿時,將時間戳最大的資料項淘汰。 2.利用一個連結串列來實現,每次新插入資料的時候將新資料插到連結串列的頭部;每次快取命中(即資料被訪問),則將資料移到連結串列頭部;那麼當連結串列滿的時候,就將連結串列尾部的資料丟棄。 3.利用連結串列和hashmap。當需要插入新的資料項的時候,如果新資料項在連結串列中存在(一般稱為命中),則把該節點移到連結串列頭部,如果不存在,則新建一個節點,放到連結串列頭部,若快取滿了,則把連結串列最後一個節點刪除即可。在訪問資料的時候,如果資料項在連結串列中存在,則把該節點移到連結串列頭部,否則返回-1。這樣一來在連結串列尾部的節點就是最近最久未訪問的資料項。
對於第一種方法,需要不停地維護資料項的訪問時間戳,另外,在插入資料、刪除資料以及訪問資料時,時間複雜度都是O(n)。對於第二種方法,連結串列在定位資料的時候時間複雜度為O(n)。所以在一般使用第三種方式來是實現LRU演算法。

實現方案

使用LinkedHashMap實現      LinkedHashMap底層就是用的HashMap加雙鏈表實現的,而且本身已經實現了按照訪問順序的儲存。此外,LinkedHashMap中本身就實現了一個方法removeEldestEntry用於判斷是否需要移除最不常讀取的數,方法預設是直接返回false,不會移除元素,所以需要重寫該方法。即當快取滿後就移除最不常用的數。
public class LRU<K,V> {

  private static final float hashLoadFactory = 0.75f;
  private LinkedHashMap<K,V> map;
  private int cacheSize;

  public LRU(int cacheSize) {
    this.cacheSize = cacheSize;
    int capacity = (int)Math.ceil(cacheSize / hashLoadFactory) + 1;
    map = new LinkedHashMap<K,V>(capacity, hashLoadFactory, true){
      private static final long serialVersionUID = 1;

      @Override
      protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > LRU.this.cacheSize;
      }
    };
  }

  public synchronized V get(K key) {
    return map.get(key);
  }

  public synchronized void put(K key, V value) {
    map.put(key, value);
  }

  public synchronized void clear() {
    map.clear();
  }

  public synchronized int usedSize() {
    return map.size();
  }

  public void print() {
    for (Map.Entry<K, V> entry : map.entrySet()) {
      System.out.print(entry.getValue() + "--");
    }
    System.out.println();
  }
}


當存在熱點資料時,LRU的效率很好,但偶發性的、週期性的批量操作會導致LRU命中率急劇下降,快取汙染情況比較嚴重。

擴充套件

1.LRU-K

LRU-K中的K代表最近使用的次數,因此LRU可以認為是LRU-1。LRU-K的主要目的是為了解決LRU演算法“快取汙染”的問題,其核心思想是將“最近使用過1次”的判斷標準擴充套件為“最近使用過K次”。 相比LRU,LRU-K需要多維護一個佇列,用於記錄所有快取資料被訪問的歷史。只有當資料的訪問次數達到K次的時候,才將資料放入快取。當需要淘汰資料時,LRU-K會淘汰第K次訪問時間距當前時間最大的資料。 資料第一次被訪問時,加入到歷史訪問列表,如果書籍在訪問歷史列表中沒有達到K次訪問,則按照一定的規則(FIFO,LRU)淘汰;當訪問歷史佇列中的資料訪問次數達到K次後,將資料索引從歷史佇列中刪除,將資料移到快取佇列中,並快取資料,快取佇列重新按照時間排序;快取資料佇列中被再次訪問後,重新排序,需要淘汰資料時,淘汰快取佇列中排在末尾的資料,即“淘汰倒數K次訪問離現在最久的資料”。 LRU-K具有LRU的優點,同時還能避免LRU的缺點,實際應用中LRU-2是綜合最優的選擇。由於LRU-K還需要記錄那些被訪問過、但還沒有放入快取的物件,因此記憶體消耗會比LRU要多。

2.two queue

Two queues(以下使用2Q代替)演算法類似於LRU-2,不同點在於2Q將LRU-2演算法中的訪問歷史佇列(注意這不是快取資料的)改為一個FIFO快取佇列,即:2Q演算法有兩個快取佇列,一個是FIFO佇列,一個是LRU佇列。當資料第一次訪問時,2Q演算法將資料快取在FIFO佇列裡面,當資料第二次被訪問時,則將資料從FIFO佇列移到LRU佇列裡面,兩個佇列各自按照自己的方法淘汰資料。 新訪問的資料插入到FIFO佇列中,如果資料在FIFO佇列中一直沒有被再次訪問,則最終按照FIFO規則淘汰;如果資料在FIFO佇列中再次被訪問到,則將資料移到LRU佇列頭部,如果資料在LRU佇列中再次被訪問,則將資料移動LRU佇列頭部,LRU佇列淘汰末尾的資料。

3.Multi Queue(MQ)

     MQ演算法根據訪問頻率將資料劃分為多個佇列,不同的佇列具有不同的訪問優先順序,其核心思想是:優先快取訪問次數多的資料。詳細的演算法結構圖如下,Q0,Q1....Qk代表不同的優先順序佇列,Q-history代表從快取中淘汰資料,但記錄了資料的索引和引用次數的佇列: 新插入的資料放入Q0,每個佇列按照LRU進行管理,當資料的訪問次數達到一定次數,需要提升優先順序時,將資料從當前佇列中刪除,加入到高一級佇列的頭部;為了防止高優先順序資料永遠不會被淘汰,當資料在指定的時間裡沒有被訪問時,需要降低優先順序,將資料從當前佇列刪除,加入到低一級的佇列頭部;需要淘汰資料時,從最低一級佇列開始按照LRU淘汰,每個佇列淘汰資料時,將資料從快取中刪除,將資料索引加入Q-history頭部。如果資料在Q-history中被重新訪問,則重新計算其優先順序,移到目標佇列頭部。Q-history按照LRU淘汰資料的索引。 MQ需要維護多個佇列,且需要維護每個資料的訪問時間,複雜度比LRU高。

LRU演算法對比

對比點

對比

命中率

LRU-2 > MQ(2) > 2Q > LRU

複雜度

LRU-2 > MQ(2) > 2Q > LRU

代價

LRU-2  > MQ(2) > 2Q > LRU

相關推薦

LRU演算法實現方式介紹

實現LRU 1.用一個數組來儲存資料,給每一個數據項標記一個訪問時間戳,每次插入新資料項的時候,先把陣列中存在的資料項的時間戳自增,並將新資料項的時間戳置為0並插入到陣列中。每次訪問陣列中的資料項的時候,將被訪問的資料項的時間戳置為0。當陣列空間已滿時,將時間戳最大的資料項淘汰。 2.利用一個連結串列

5.6-全棧Java筆記:內部類的實現方式

java一般情況,我們把類定義成獨立的單元。有些情況下,我們把一個類放在另一個類的內部定義,稱為內部類(innerclasses)。內部類的作用1.內部類提供了更好的封裝。只能讓外部類直接訪問,不允許同一個包中的其他類直接訪問。2.內部類可以直接訪問外部類的私有屬性,內部類被當成其外部類的成員。 但外部類不能

JavaScript中的單體模式實現方式

ret div 劃分 scrip diff different 不同的 如果 get 1 /* 2 1 簡單單體 3 */ 4 var Singleton = { 5 attr1: 1 , 6 method1:funct

RxJS的另外實現方式(三)——性能最高的庫

如何 www table fas set export llb const events 接上篇 RxJS的另外四種實現方式(二)——代碼最小的庫(續) 代碼最小的庫rx4rx-lite雖然在性能測試中超過了callbag,但和most庫較量的時候卻落敗了,於是我下載了

Java實現多線程的實現方式

lis star 維護 invoke 1.0 threads arraylist urn fix 以計算0到1000之間的和為例 import java.util.ArrayList; import java.util.LinkedList; import java.uti

Brute-Force模式匹配演算法實現方式

1. public static int indexOf(String mainStr,String subString,int start) { if((mainStr.length()<subString.length()) || mainStr==null || subStr

分散式鎖簡單三實現方式介紹

版權宣告:本文為博主原創文章,未經博主允許不得轉載。    https://blog.csdn.net/u010870518/article/details/79036337 很多小夥伴在學習Java的時候,總是感覺Java多執行緒在實際的業務中很少使用,以至於不會花太

Java併發程式設計(二)多執行緒實現方式

Java實現多執行緒的方式 Java實現多執行緒的方式有4種: 繼承Thread方法、實現Runnable介面、實現Callable介面並通過FutureTask建立執行緒、使用ExecutorService。 其中,前兩種執行緒執行結果沒有返回值,後兩種是有返回值的。 1、繼承Th

RxJS的另外實現方式(三)——效能最高的庫

程式碼最小的庫rx4rx-lite雖然在效能測試中超過了callbag,但和most庫較量的時候卻落敗了,於是我下載了most庫,要解開most庫效能高的原因。 我們先上一組測試資料,這是在我的windows10 上面跑的 dataflow for 10000

RxJS的另外實現方式(六)——使用Stream類實現

該實現方式與之前幾種不同的,該實現方式僅針對Nodejs環境。在Nodejs環境中,提供了Stream類,包括Readable、Transform、Writeable等子類都是可擴充套件的。從字面上看,正好對應Rx中的生產者、傳遞者、消費者。 實現該庫的起因是

Java多執行緒之實現方式

介紹 繼承Thread類,並重寫其run方法 實現Runnable介面 實現Callable介面通過FutureTask包裝器來建立Thread執行緒 執行緒池,使用ExecutorService、Callable、Future實現有返回結果的多執行緒。 其

斐波那契數列的實現方式(C語言)

斐波那契數列是一組第一位和第二位為1,從第三位開始,後一位是前兩位和的一組遞增數列, 像這樣的:1、1、2、3、5、8、13、21、34、55… 今天,我們用四種方式來進行實現: 1.遞迴 int Fibon1(int n) { if (n == 1 || n

JAVA多執行緒的實現方式

1.繼承Thread  重寫run()方法,該run方法表示執行緒要完成的任務。建立執行緒物件,呼叫物件的start()方法來啟動執行緒。 2.Runnable介面 重寫介面中run方法。建立Runable例項類的例項,並依此例項作為Thread的target來建立Th

分散式鎖簡單入門以及三實現方式介紹

分散式鎖應該具備哪些條件 在分析分散式鎖的三種實現方式之前,先了解一下分散式鎖應該具備哪些條件: 1、在分散式系統環境下,一個方法在同一時間只能被一個機器的一個執行緒執行;  2、高可用的獲取鎖與釋放鎖;  3、高效能的獲取鎖與釋放鎖;  4、具備可重入特性;  5、具備鎖

【原創】redis庫存操作,分散式鎖的實現方式[連載一]--基於zookeeper實現分散式鎖

一、背景 在電商系統中,庫存的概念一定是有的,例如配一些商品的庫存,做商品秒殺活動等,而由於庫存操作頻繁且要求原子性操作,所以絕大多數電商系統都用Redis來實現庫存的加減,最近公司專案做架構升級,以微服務的形式做分散式部署,對庫存的操作也單獨封裝為一個微服務,這樣在高併發情況下,加減庫存時,就會出現超賣等

【原創】redis庫存操作,分布式鎖的實現方式[連載一]--基於zookeeper實現分布式鎖

zookeepe operation iat 並發 method logger 方案 nag 概念 一、背景 在電商系統中,庫存的概念一定是有的,例如配一些商品的庫存,做商品秒殺活動等,而由於庫存操作頻繁且要求原子性操作,所以絕大多數電商系統都用Redis來實現庫存的加減,

【原創】redis庫存操作,分散式鎖的實現方式[連載二]--基於Redisson實現分散式鎖

一、redisson介紹 redisson實現了分散式和可擴充套件的java資料結構,支援的資料結構有:List, Set, Map, Queue, SortedSet, ConcureentMap, Lock, AtomicLong, CountDownLatch。並且是執行緒安全的,底層使用N

【連載】redis庫存操作,分散式鎖的實現方式[三]--基於Redis watch機制實現分散式鎖

一、redis的事務介紹 1、 Redis保證一個事務中的所有命令要麼都執行,要麼都不執行。如果在傳送EXEC命令前客戶端斷線了,則Redis會清空事務佇列,事務中的所有命令都不會執行。而一旦客戶端傳送了EXEC命令,所有的命令就都會被執行,即使此後客戶端斷線也沒關係,因為Redis中已經記錄了所有要執行的

內部類的實現方式

<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> //成員內部類......相當於非靜態方法class MemberInner {

單例模式(Singleton Pattern)典型的實現方式

一、簡介: 引入百度百科對單例模式的介紹: 單例模式(Singleton Pattern)是一種常用的軟體設計模式。《設計模式》一書中對其介紹:“保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。” 在某些時候,一個系統只有一個例項是很重要且必要的,比如:Wind