自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊聊緩存淘汰算法-LRU 實(shí)現(xiàn)原理

存儲(chǔ) 數(shù)據(jù)管理 算法
我們常用緩存提升數(shù)據(jù)查詢速度,由于緩存容量有限,當(dāng)緩存容量到達(dá)上限,就需要?jiǎng)h除部分?jǐn)?shù)據(jù)挪出空間,這樣新數(shù)據(jù)才可以添加進(jìn)來。緩存數(shù)據(jù)不能隨機(jī)刪除,一般情況下我們需要根據(jù)某種算法刪除緩存數(shù)據(jù)。常用淘汰算法有 LRU,LFU,FIFO,這篇文章我們聊聊 LRU 算法。

 [[315530]]

01、前言

我們常用緩存提升數(shù)據(jù)查詢速度,由于緩存容量有限,當(dāng)緩存容量到達(dá)上限,就需要?jiǎng)h除部分?jǐn)?shù)據(jù)挪出空間,這樣新數(shù)據(jù)才可以添加進(jìn)來。緩存數(shù)據(jù)不能隨機(jī)刪除,一般情況下我們需要根據(jù)某種算法刪除緩存數(shù)據(jù)。常用淘汰算法有 LRU,LFU,FIFO,這篇文章我們聊聊 LRU 算法。

02、LRU 簡(jiǎn)介

LRU 是 Least Recently Used 的縮寫,這種算法認(rèn)為最近使用的數(shù)據(jù)是熱門數(shù)據(jù),下一次很大概率將會(huì)再次被使用。而最近很少被使用的數(shù)據(jù),很大概率下一次不再用到。當(dāng)緩存容量的滿時(shí)候,優(yōu)先淘汰最近很少使用的數(shù)據(jù)。

假設(shè)現(xiàn)在緩存內(nèi)部數(shù)據(jù)如圖所示:

聊聊緩存淘汰算法-LRU 實(shí)現(xiàn)原理

 

這里我們將列表第一個(gè)節(jié)點(diǎn)稱為頭結(jié)點(diǎn),最后一個(gè)節(jié)點(diǎn)為尾結(jié)點(diǎn)。

當(dāng)調(diào)用緩存獲取 key=1 的數(shù)據(jù),LRU 算法需要將 1 這個(gè)節(jié)點(diǎn)移動(dòng)到頭結(jié)點(diǎn),其余節(jié)點(diǎn)不變,如圖所示。

聊聊緩存淘汰算法-LRU 實(shí)現(xiàn)原理

 

然后我們插入一個(gè) key=8 節(jié)點(diǎn),此時(shí)緩存容量到達(dá)上限,所以加入之前需要先刪除數(shù)據(jù)。由于每次查詢都會(huì)將數(shù)據(jù)移動(dòng)到頭結(jié)點(diǎn),未被查詢的數(shù)據(jù)就將會(huì)下沉到尾部節(jié)點(diǎn),尾部的數(shù)據(jù)就可以認(rèn)為是最少被訪問的數(shù)據(jù),所以刪除尾結(jié)點(diǎn)的數(shù)據(jù)。

 

聊聊緩存淘汰算法-LRU 實(shí)現(xiàn)原理

 

 

然后我們直接將數(shù)據(jù)添加到頭結(jié)點(diǎn)。

聊聊緩存淘汰算法-LRU 實(shí)現(xiàn)原理

 

這里總結(jié)一下 LRU 算法具體步驟:

  • 新數(shù)據(jù)直接插入到列表頭部
  • 緩存數(shù)據(jù)被命中,將數(shù)據(jù)移動(dòng)到列表頭部
  • 緩存已滿的時(shí)候,移除列表尾部數(shù)據(jù)。

03、LRU 算法實(shí)現(xiàn)

上面例子中可以看到,LRU 算法需要添加頭節(jié)點(diǎn),刪除尾結(jié)點(diǎn)。而鏈表添加節(jié)點(diǎn)/刪除節(jié)點(diǎn)時(shí)間復(fù)雜度 O(1),非常適合當(dāng)做存儲(chǔ)緩存數(shù)據(jù)容器。但是不能使用普通的單向鏈表,單向鏈表有幾點(diǎn)劣勢(shì):

  1. 每次獲取任意節(jié)點(diǎn)數(shù)據(jù),都需要從頭結(jié)點(diǎn)遍歷下去,這就導(dǎo)致獲取節(jié)點(diǎn)復(fù)雜度為 O(N)。
  2. 移動(dòng)中間節(jié)點(diǎn)到頭結(jié)點(diǎn),我們需要知道中間節(jié)點(diǎn)前一個(gè)節(jié)點(diǎn)的信息,單向鏈表就不得不再次遍歷獲取信息。

針對(duì)以上問題,可以結(jié)合其他數(shù)據(jù)結(jié)構(gòu)解決。

使用散列表存儲(chǔ)節(jié)點(diǎn),獲取節(jié)點(diǎn)的復(fù)雜度將會(huì)降低為 O(1)。節(jié)點(diǎn)移動(dòng)問題可以在節(jié)點(diǎn)中再增加前驅(qū)指針,記錄上一個(gè)節(jié)點(diǎn)信息,這樣鏈表就從單向鏈表變成了雙向鏈表。

綜上使用雙向鏈表加散列表結(jié)合體,數(shù)據(jù)結(jié)構(gòu)如圖所示:

 

聊聊緩存淘汰算法-LRU 實(shí)現(xiàn)原理

 

 

在雙向鏈表中特意增加兩個(gè)『哨兵』節(jié)點(diǎn),不用來存儲(chǔ)任何數(shù)據(jù)。使用哨兵節(jié)點(diǎn),增加/刪除節(jié)點(diǎn)的時(shí)候就可以不用考慮邊界節(jié)點(diǎn)不存在情況,簡(jiǎn)化編程難度,降低代碼復(fù)雜度。

LRU 算法實(shí)現(xiàn)代碼如下,為了簡(jiǎn)化 key ,val 都認(rèn)為 int 類型。

public class LRUCache {    Entry head, tail;    int capacity;    int size;    Map<Integer, Entry> cache;    public LRUCache(int capacity) {        this.capacity = capacity;        // 初始化鏈表        initLinkedList();        size = 0;        cache = new HashMap<>(capacity + 2);    }    /**     * 如果節(jié)點(diǎn)不存在,返回 -1.如果存在,將節(jié)點(diǎn)移動(dòng)到頭結(jié)點(diǎn),并返回節(jié)點(diǎn)的數(shù)據(jù)。     *     * @param key     * @return     */    public int get(int key) {        Entry node = cache.get(key);        if (node == null) {            return -1;        }        // 存在移動(dòng)節(jié)點(diǎn)        moveToHead(node);        return node.value;    }    /**     * 將節(jié)點(diǎn)加入到頭結(jié)點(diǎn),如果容量已滿,將會(huì)刪除尾結(jié)點(diǎn)     *     * @param key     * @param value     */    public void put(int key, int value) {        Entry node = cache.get(key);        if (node != null) {            node.value = value;            moveToHead(node);            return;        }        // 不存在。先加進(jìn)去,再移除尾結(jié)點(diǎn)        // 此時(shí)容量已滿 刪除尾結(jié)點(diǎn)        if (size == capacity) {            Entry lastNode = tail.pre;            deleteNode(lastNode);            cache.remove(lastNode.key);            size--;        }        // 加入頭結(jié)點(diǎn)        Entry newNode = new Entry();        newNode.key = key;        newNode.value = value;        addNode(newNode);        cache.put(key, newNode);        size++;    }    private void moveToHead(Entry node) {        // 首先刪除原來節(jié)點(diǎn)的關(guān)系        deleteNode(node);        addNode(node);    }    private void addNode(Entry node) {        head.next.pre = node;        node.next = head.next;        node.pre = head;        head.next = node;    }    private void deleteNode(Entry node) {        node.pre.next = node.next;        node.next.pre = node.pre;    }    public static class Entry {        public Entry pre;        public Entry next;        public int key;        public int value;        public Entry(int key, int value) {            this.key = key;            this.value = value;        }        public Entry() {        }    }    private void initLinkedList() {        head = new Entry();        tail = new Entry();        head.next = tail;        tail.pre = head;    }    public static void main(String[] args) {        LRUCache cache = new LRUCache(2);        cache.put(1, 1);        cache.put(2, 2);        System.out.println(cache.get(1));        cache.put(3, 3);        System.out.println(cache.get(2));    }}

04、LRU 算法分析

緩存命中率是緩存系統(tǒng)的非常重要指標(biāo),如果緩存系統(tǒng)的緩存命中率過低,將會(huì)導(dǎo)致查詢回流到數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)的壓力升高。

結(jié)合以上分析 LRU 算法優(yōu)缺點(diǎn)。

LRU 算法優(yōu)勢(shì)在于算法實(shí)現(xiàn)難度不大,對(duì)于對(duì)于熱點(diǎn)數(shù)據(jù), LRU 效率會(huì)很好。

LRU 算法劣勢(shì)在于對(duì)于偶發(fā)的批量操作,比如說批量查詢歷史數(shù)據(jù),就有可能使緩存中熱門數(shù)據(jù)被這些歷史數(shù)據(jù)替換,造成緩存污染,導(dǎo)致緩存命中率下降,減慢了正常數(shù)據(jù)查詢。

05、LRU 算法改進(jìn)方案

以下方案來源與 MySQL InnoDB LRU 改進(jìn)算法

將鏈表拆分成兩部分,分為熱數(shù)據(jù)區(qū),與冷數(shù)據(jù)區(qū),如圖所示。

聊聊緩存淘汰算法-LRU 實(shí)現(xiàn)原理

 

改進(jìn)之后算法流程將會(huì)變成下面一樣:

  1. 訪問數(shù)據(jù)如果位于熱數(shù)據(jù)區(qū),與之前 LRU 算法一樣,移動(dòng)到熱數(shù)據(jù)區(qū)的頭結(jié)點(diǎn)。
  2. 插入數(shù)據(jù)時(shí),若緩存已滿,淘汰尾結(jié)點(diǎn)的數(shù)據(jù)。然后將數(shù)據(jù)插入冷數(shù)據(jù)區(qū)的頭結(jié)點(diǎn)。
  3. 處于冷數(shù)據(jù)區(qū)的數(shù)據(jù)每次被訪問需要做如下判斷:若該數(shù)據(jù)已在緩存中超過指定時(shí)間,比如說 1 s,則移動(dòng)到熱數(shù)據(jù)區(qū)的頭結(jié)點(diǎn)。若該數(shù)據(jù)存在在時(shí)間小于指定的時(shí)間,則位置保持不變。

對(duì)于偶發(fā)的批量查詢,數(shù)據(jù)僅僅只會(huì)落入冷數(shù)據(jù)區(qū),然后很快就會(huì)被淘汰出去。熱門數(shù)據(jù)區(qū)的數(shù)據(jù)將不會(huì)受到影響,這樣就解決了 LRU 算法緩存命中率下降的問題。

其他改進(jìn)方法還有 LRU-K,2Q,LIRS 算法,感興趣同學(xué)可以自行查閱。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2022-06-17 07:49:14

緩存LRU

2015-07-29 10:31:16

Java緩存算法

2020-10-30 11:30:15

Least Recen

2022-05-09 19:59:15

RedisLRU 算法

2021-03-01 18:42:02

緩存LRU算法

2009-07-23 11:11:18

LRU緩存

2019-10-31 10:25:19

算法Java數(shù)據(jù)結(jié)構(gòu)

2022-08-30 13:48:16

LinuxMySQL內(nèi)存

2021-07-11 18:06:18

緩存過期淘汰

2024-08-05 11:14:45

2024-12-23 15:05:29

2021-11-08 11:21:18

redis 淘汰算法

2015-07-15 10:19:16

Java代碼使用緩存

2021-07-14 14:05:24

Fragment項(xiàng)目結(jié)構(gòu)

2022-02-18 08:26:12

TopK數(shù)組面試題

2023-07-06 12:39:14

RedisLRULFU

2024-03-15 07:17:51

MySQLLRU算法緩存池

2024-10-16 11:28:42

2024-09-13 16:47:06

模型量化AI

2021-07-15 14:29:06

LRU算法
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)