日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法,選擇最近最久未使用的頁面予以淘汰。當(dāng)限定的空間已存滿數(shù)據(jù)時(shí),應(yīng)當(dāng)把最久沒有被訪問到的數(shù)據(jù)淘汰。

簡單描述一下在《操作系統(tǒng)》這本書里面對于LRU算法的解說。

假定系統(tǒng)為某進(jìn)程分配了3個(gè)物理塊,進(jìn)程運(yùn)行時(shí)的頁面走向?yàn)?7 0 1 2 0 3 0 4,開始時(shí)3個(gè)物理塊均為空,那么LRU 算法是如下工作的:

LRU算法到底是怎么一回事?

 

這就是最基本的LRU的磁盤調(diào)度邏輯,該算法運(yùn)用領(lǐng)域比較廣泛比如redis的內(nèi)存淘汰策略等等,該算法也是面試中面試官常常用來考驗(yàn)面試者代碼能力和對LRU算法的正確理解。

以下我主要以為雙向鏈表+HashMap的方式手撕一個(gè)時(shí)間復(fù)雜度為O(1)的LRU算法。

在JAVA中,其實(shí)LinkedHashMap已經(jīng)實(shí)現(xiàn)了LRU緩存淘汰算法,需要在構(gòu)造方法第三個(gè)參數(shù)傳入true( accessOrder = true;),表示按照時(shí)間順序訪問。可以直接繼承LinkedHashMap來實(shí)現(xiàn)。

public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {

    private int capacity;

    LRULinkedHashMap(int capacity) {
        //true是表示按照訪問時(shí)間排序,
        super(capacity, 0.75f, true);
        //傳入指定的緩存最大容量
        this.capacity = capacity;
    }

    /**
     * 實(shí)現(xiàn)LRU的關(guān)鍵方法,如果map里面的元素個(gè)數(shù)大于了緩存最大容量,則刪除鏈表的頂端元素
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
}

算法設(shè)計(jì)思路

  • 訪問某個(gè)節(jié)點(diǎn)時(shí),將其從原來的位置刪除,并重新插入到鏈表頭部。
  • 這樣就能保證鏈表尾部存儲的就是最近最久未使用的節(jié)點(diǎn),當(dāng)節(jié)點(diǎn)數(shù)量大于緩存最大空間時(shí)就淘汰鏈表尾部的節(jié)點(diǎn)。
  • 為了使刪除操作時(shí)間復(fù)雜度為 O(1),就不能采用遍歷的方式找到某個(gè)節(jié)點(diǎn)。
  • HashMap 存儲著 Key 到節(jié)點(diǎn)的映射,通過 Key 就能以 O(1) 的時(shí)間得到節(jié)點(diǎn),然后再以 O(1) 的時(shí)間將其從雙向隊(duì)列中刪除。
LRU算法到底是怎么一回事?

 

一.構(gòu)建雙向鏈表Node節(jié)點(diǎn)

    /**
     * 定義雙向鏈表其中K為Map中的K 降低查找時(shí)間復(fù)雜度
     */
    class Node {
        K k;
        V v;
        Node pre;
        Node next;

        Node(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }

二.定義變量

//定義緩存大小
private int size;
// 存儲K和Node節(jié)點(diǎn)的映射 Node中會存放KV
private HashMap<K, Node> map;
private Node head;
private Node tail;

三.初始化結(jié)構(gòu)體

XLRUCache(int size) {
    this.size = size;
    map = new HashMap<>();
}

四.添加元素

/**
 * 添加元素
 * 1.元素存在,將元素移動到隊(duì)尾
 * 2.不存在,判斷鏈表是否滿。
 * 如果滿,則刪除隊(duì)首(head)元素,新元素放入隊(duì)尾元素
 * 如果不滿,放入隊(duì)尾(tail)元素
 */
public void put(K key, V value) {
    Node node = map.get(key);
    if (node != null) {
        //更新值
        node.v = value;
        moveNodeToTail(node);
    } else {
        Node newNode = new Node(key, value);
        //鏈表滿,需要?jiǎng)h除首節(jié)點(diǎn)
        if (map.size() == size) {
            Node delHead = removeHead();
            map.remove(delHead.k);
        }
        addLast(newNode);
        map.put(key, newNode);
    }
}
  • 移動元素到鏈表尾部
 public void moveNodeToTail(Node node) {
        if (tail == node) {
            return;
        }
      // 頭節(jié)點(diǎn)直接置空
        if (head == node) {   // 備注一
            head = node.next;
            head.pre = null; 
        } else {              // 備注一
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }
     // 備注三
        node.pre = tail; 
        node.next = null;
        tail.next = node;
        tail = node;
    }
  • 看備注一&備注三如下圖
LRU算法到底是怎么一回事?

 

  • 看備注二&備注三如下圖
LRU算法到底是怎么一回事?

 

  • 刪除頭節(jié)點(diǎn)
 public Node removeHead() {
       // 空鏈表
        if (head == null) {
            return null;
        }
        Node res = head;
       // 只有一個(gè)節(jié)點(diǎn)
        if (head == tail) {
            head = null;
            tail = null;
        } else {
        // 多個(gè)節(jié)點(diǎn)
            head = res.next;
            head.pre = null;
            res.next = null;
        }
        return res;
  }

map.remove(delHead.k): 刪除Map中的Kv映射關(guān)系

  • 添加新節(jié)點(diǎn)
   public void addLast(Node newNode) {
       // 添加節(jié)點(diǎn)為空節(jié)點(diǎn)直接返回
        if (newNode == null) {
            return;
        }
       // 如果鏈表為空則直接添加
        if (head == null) {
            head = newNode;
            tail = newNode;
        } else {
            // 不為空則尾部添加
            tail.next = newNode;
            newNode.pre = tail;
            tail = newNode;
        }
    }

如果鏈表為空則將該元素設(shè)置成表頭元素同時(shí)也是表尾元素。

五.獲取元素

public V get(K key) {
    Node node = map.get(key);
    if (node != null) {
        moveNodeToTail(node);
        return node.v;
    }
    return null;
}

調(diào)度訪問后的節(jié)點(diǎn)需要移動到鏈表尾部。

完整代碼

import java.util.HashMap;

/**
 * @Auther: Xianglei
 * @Company:
 * @Date: 2020/6/27 14:52
 * @Version 1.0
 */
public class XLRUCache<K, V> {
    private int size;
    // 存儲K和Node節(jié)點(diǎn)的映射 Node中會存放KV
    private HashMap<K, Node> map;
    private Node head;
    private Node tail;

    XLRUCache(int size) {
        this.size = size;
        map = new HashMap<>();
    }

    /**
     * 添加元素
     * 1.元素存在,將元素移動到隊(duì)尾
     * 2.不存在,判斷鏈表是否滿。
     * 如果滿,則刪除隊(duì)首元素,放入隊(duì)尾元素,刪除更新哈希表
     * 如果不滿,放入隊(duì)尾元素,更新哈希表
     */
    public void put(K key, V value) {
        Node node = map.get(key);
        if (node != null) {
            //更新值
            node.v = value;
            moveNodeToTail(node);
        } else {
            Node newNode = new Node(key, value);
            //鏈表滿,需要?jiǎng)h除首節(jié)點(diǎn)
            if (map.size() == size) {
                Node delHead = removeHead();
                map.remove(delHead.k);
            }
            addLast(newNode);
            map.put(key, newNode);
        }
    }

    public V get(K key) {
        Node node = map.get(key);
        if (node != null) {
            moveNodeToTail(node);
            return node.v;
        }
        return null;
    }

    public void addLast(Node newNode) {
        if (newNode == null) {
            return;
        }
        if (head == null) {
            head = newNode;
            tail = newNode;
        } else {
            tail.next = newNode;
            newNode.pre = tail;
            tail = newNode;
        }
    }

    public void moveNodeToTail(Node node) {
        if (tail == node) {
            return;
        }
        if (head == node) {
            head = node.next;
            head.pre = null;
        } else {
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }
        node.pre = tail;
        node.next = null;
        tail.next = node;
        tail = node;
    }

    public Node removeHead() {
        if (head == null) {
            return null;
        }
        Node res = head;
        if (head == tail) {
            head = null;
            tail = null;
        } else {
            head = res.next;
            head.pre = null;
            res.next = null;
        }
        return res;
    }

    /**
     * 定義雙向鏈表
     */
    class Node {
        K k;
        V v;
        Node pre;
        Node next;

        Node(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }
}

測試

LRU算法到底是怎么一回事?

 

至此,你應(yīng)該已經(jīng)掌握 LRU 算i法的思想和實(shí)現(xiàn)過程了,這里面最重要的一點(diǎn)是理清楚雙向鏈表和HasMap的映射關(guān)系以及節(jié)點(diǎn)移動操作。自此,你知道為什么用雙向鏈表了嗎?


更多精彩好文盡在: Java編程之道

分享到:
標(biāo)簽:算法 LRU
用戶無頭像

網(wǎng)友整理

注冊時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定