Leetcode.146.LRU缓存机制

LRU缓存机制

  • 前言
  • 一、什么是LRU?
  • 二、Map+链表
    • 1、思想
    • 2、源码
  • 三、LinkedHashMap
    • 1、思想
    • 2、源码
  • 五、LinkedHashMap自带removeEldestEntry
    • 1、源码
    • 2、详解
  • 总结

前言

leetcode打卡146题,LRU缓存机制。考察HashMap+双向链表的应用,HashMap快速查找,双向链表记录顺序。将链表的Node作为Map的value。
所以通过key快速找到value即Node,找到了Node就可以对其进行快速删除。
或者使用LinkedHashMap类来完成的有序记录。

一、什么是LRU?

最近最久未使用(least recently used,LRU)置换算法,该算法来至于操作系统中。OS为了处理进程的并发,采用了一个程序只加载一部分进入内存,以提高内存的利用率以及系统的吞吐量。当CPU需要一个程序的其它部分来执行时,则产生缺页中断,将所缺页面直接调入或置换,这要看系统给一个进程所分配的物理块是多少。
这个算法顾名思义,就是当一个进程所分配的物理块被占满时,而系统产生缺页中断时,系统就要将最近最久未使用的页面与新页面进行置换。
这样的做的目的是减少缺页中断次数从而减少系统开销,而为什么这样做能减少缺页中断次数,是因为局部性原理。
1)时间局部性,程序中的某条指令一旦执行,不久之后该指令可能再次执行;某数据被访问过,不久之后该数据可能再次被访问。产生时间局部性的典型原因是程序中存在着大量的循环操作。
Leetcode.146.LRU缓存机制_第1张图片
注:对内存管理(包含LRU类似页面置换算法)甚至操作系统感兴趣或有什么疑惑可以进入我的其它博客内存管理

二、Map+链表

1、思想

Map用于快速查找,链表用于记录顺序。
将链表的Node作为Map的value,则可以通过key来找到Node,再将找到的Node进行操作(这里采用双向链表方便这些删除Node),以达到记录顺序即多久没使用的一个时间排序。
Leetcode.146.LRU缓存机制_第2张图片

2、源码

package com.xhu.offer.offerII;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

//最近最少使用缓存
public class LRUCache {
    //设计题:双向链表+HashMap即可解决。HashMap快速定位节点位置,且可快速将节点换到最后。采用前插法+dummyHead来替换currPoint+非空节点处理。
    //总结:主要思想简单,如果熟练链表操作,那么还需要注意一些小细节,比如超过了capacity,除了要断链,还要去掉cache记录的元素。以及size--;
    //总结:这种加上头尾空节点,可以少处理很多事情。
    int capacity;
    Map<Integer, Node> cache;
    Node dummyHead, dummyLast;
    int size;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new HashMap<>();

        dummyHead = new Node();
        dummyLast = new Node();

        dummyLast.prev = dummyHead;
        dummyHead.next = dummyLast;

        size = 0;
    }

    public int get(int key) {
        if (!cache.containsKey(key)) return -1;
        Node node = cache.get(key);
        if (dummyHead.next != node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;

            node.next = dummyHead.next;
            node.next.prev = node;
            dummyHead.next = node;
            node.prev = dummyHead;
        }
        return node.val;
    }

    public void put(int key, int value) {
        if (!cache.containsKey(key)) {

            Node node = new Node(key, value);
            cache.put(key, node);
            node.next = dummyHead.next;
            node.next.prev = node;

            dummyHead.next = node;
            node.prev = dummyHead;
            if (++size > capacity) {
                //还需要删掉cache里面存的记录
                cache.remove(dummyLast.prev.key);
                System.out.println(Arrays.toString(cache.keySet().toArray()));
                //除了删除cache里的记录,还要size--;
                size--;
                //除了要size--,还要再删除链表中的节点
                dummyLast.prev.prev.next = dummyLast;
                dummyLast.prev = dummyLast.prev.prev;
            }
            return;
        }

        Node node = cache.get(key);
        node.val = value;
        if (dummyHead.next != node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;

            node.next = dummyHead.next;
            node.next.prev = node;
            dummyHead.next = node;
            node.prev = dummyHead;
        }
    }

    //双向链表
    class Node {
        int key;
        int val;
        Node next;
        Node prev;

        Node() {
        }

        Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
}
   

三、LinkedHashMap

1、思想

LinkedHashMap是记录了顺序的Map。
1)get、put
get和put已有时,则需要删掉原来在前面的Map.Entry,然后把这个重新put一下,也就是放在链尾。如果put新时,则需要直接加入(map.size()删除,然后把新put进入。

2、源码

public class LRUCache146 {
    private int cap;
    Map<Integer,Integer> cache = new LinkedHashMap<>();
    public LRUCache146(int capacity) {
        cap = capacity;
    }

    public int get(int key) {
        Integer data = cache.get(key) ;
        if(cache.containsKey(key)){
            cache.remove(key);
            cache.put(key,data);
            return data;
        }
        return -1;

    }

    public void put(int key, int value) {
        if(cache.containsKey(key)){
            cache.remove(key);
        }
        else if(cap == cache.size()){
            //cache.remove(cache.keySet().toArray()[0]);
            cache.remove(cache.keySet().iterator().next());
        }
        cache.put(key,value);
    }

    public static void main(String[] args) {
    }
}

五、LinkedHashMap自带removeEldestEntry

1、源码

class LRUCache {
    int capacity;
    LinkedHashMap<Integer, Integer> cache;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return cache.size() > capacity;
            }
        };
    }

    public int get(int key) {
        return cache.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        cache.put(key, value);
    }
}

2、详解

LinkedHashMap中自带移除旧元素,我们需要重写removeEldestEntry()方法,需要我们让其知道什么条件下才让Map移除最久未被访问的。这个条件就是cache.size() > capacity

总结

Map有最快的查找速度,list有记录顺序的功能,栈有后进先出的特点,队列有先进先出的特点等等,善于把这些特长利用起来,对问题一一匹配解决。
对于Map的变种和排序有一篇比较详细的文章Map及变种排序博客

你可能感兴趣的:(数据机构与算法,leetcode,缓存,算法,lru,java)