基于双向链表和哈希表实现LRU

LRU

LRU的定义
  • LRU是一种页面置换算法,即当内存中没有空闲页面但又需要内存时,操作系统会选择内存中的一个页面进行淘汰。淘汰页面的规则称为页面置换算法,同样,这种淘汰页面的规则也适用于缓存淘汰。LRU全称是最近最久未使用算法,LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。根据局部性原理,当一个页面最近被访问,他很有可能再次被访问。因此LRU通常性能比较好。
LRU实现分析
  • 我们假设将内存中所有到访的页面放置到链表中,新加入的数据尾插。内存中已经存在的页面被访问时,会将其移向链表的尾部。也就是说那些刚加入或者刚访问过的页面都会靠近链表尾部,因此最久未被使用的页面自然就在链表头部。当需要淘汰一个页面时,将链表头部的页面丢弃。
LRU基于链表和哈希表的实现
package collection.List;

import java.util.HashMap;

public class LRU<T> {

    private int capacity;
    private HashMap<Integer, ListNode> map;
    private ListNode head; // 链表的头结点,next指向链表中的第一个元素
    private ListNode tail; //链表的尾节点,链表的最后一个元素的next指向tail
    // 设置一个head和tail是为了方便对链表中的头结点和尾节点进行操作。

    private class ListNode { //双向链表,用来存储缓存的值
        int key;
        int val;
        ListNode prev;
        ListNode next;
        public ListNode(){

        }
        public ListNode(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
    public LRU(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new ListNode();
        tail = new ListNode();
        head.next = tail; // 初始情况下,头结点的next指向tail表示链表当前为空
        tail.prev = head; // 初始情况下,tail节点的prev指向head
    }

    private void removeNode(ListNode node) { // 在链表中的移除node节点
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void addNodeToLast(ListNode node) {
        node.prev = tail.prev; // 首先将node的前驱节点设置为链表中的最后一个节点
        node.prev.next = node; // 然后设置插入节点为原链表中最后一个节点的next
        node.next = tail; //更新tail为当前插入节点的next
        tail.prev= node; //更新tail的prev域
    }

    public void  moveNodeToLast(ListNode node) {
        removeNode(node); // 删除原节点
        addNodeToLast(node); //将原节点的副本加入到链表尾部
    }

    public int get(int key) {
        if(map.containsKey(key)) { // 如果值存在,则将其移动到链表尾部,并返回值
            ListNode node = map.get(key);
            moveNodeToLast(node);
            return node.val;
        } else {
            return -1;
        }
    }

    public void put(int key, int value) {
        if(map.containsKey(key)){ // 如果哈希表记录中找到缓存中包含key
            ListNode node = map.get(key);
            node.val = value; // 则重新设置key所对应的value
            map.replace(key, node);
            moveNodeToLast(node); // 将原节点删除,并将其副本加入链表尾部
            return;
        }
        if(map.size() == capacity){ // 如果哈希表中没有找到缓存,且链表满,则将链头节点移除,并在哈希表中将其删除
            map.remove(head.next.key);
            removeNode(head.next);
        }

        ListNode node = new ListNode(key, value);
        map.put(key, node); // 将新节点用哈希表记录
        addNodeToLast(node); // 新节点加入链表尾部
    }
}
时间并不会因为你的迷茫和迟疑而停留,就在你看这篇文章的同时,不知道有多少人在冥思苦想,在为算法废寝忘食,不知道有多少人在狂热地拍着代码,不知道又有多少提交一遍又一遍地刷新着OJ的status页面…… 没有谁生来就是神牛,而千里之行,始于足下!
原文地址:https://www.cnblogs.com/bianjunting/p/14662066.html