LeetCode之LRU Cache 最近最少使用算法 缓存设计

设计并实现最近最久未使用(Least Recently Used)缓存。html

 

题目描述:java

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.node

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.mysql

设计并实现最近最久未使用的缓存数据结构,支持 get 和 set 操做.算法

get()-若是 key 存在,返回对应的 value 值,不然返回 -1.sql

set()-插入 key 对应的 value 到缓存中,若是缓存已满,将最近最久未使用的元素从缓存中移除。缓存

要实现这个设计,咱们先回顾一下大学课堂上的知识。
LRU,即最近最少使用,是操做系统内存管理的一种页面置换算法,
常见的页面置换算法,最佳置换算法(OPT,理想置换算法),先进先出置换算法(FIFO),
最近最久未使用算法(LRU),最少使用算法。数据结构


其中,最佳置换算法是一种理想状况下的页面置换算法,实际上不可能实现。该算法的基本思想是发生缺页时,有些页面在内存中,其中有一页将很快被访问(也包含紧接着的下一条指令的那页),而其余页面则可能要到十、100或者1000条指令后才会被访问,每一个页面均可以用在该页面首次被访问前所要执行的指令数进行标记。最佳页面置换算法规定标记最大的页应该被置换。但当缺页发生时,操做系统没法知道各个页面下一次是在何时被访问。这个算法没法实现,但能够用于对可实现算法的性能进行衡量。性能

另外两种主要算法,LFU算法-实现缓存,FIFO算法-实现缓存,能够查看这里测试

LRU的实现方法有不少,传统的LRU实现方法:

1.计数器。最简单的状况是使每一个页表项对应一个使用时间字段,并给CPU增长一个逻辑时钟或计数器。每次存储访问,该时钟都加1。每当访问一个页面时,时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样咱们就能够始终保留着每一个页面最后访问的“时间”。在置换页面时,选择该时间值最小的页面。
2.栈。用一个栈保留页号。每当访问一个页面时,就把它从栈中取出放在栈顶上。这样一来,栈顶老是放有目前使用最多的页,而栈底放着目前最少使用的页。因为要从栈的中间移走一项,因此要用具备头尾指针的双向链连起来。

(1)使用 LinkedHashMap实现Lrucache

Java语言能够利用 LinkedHashMap, LinkedHashMap 是有序的哈希表,能够保存记录的插入顺序,而且按使用顺序排列。
重写其中的removeEldestEntry(Map.Entry)方法,就能够实现LRU算法。

在Mysql Jdbc Util和Apache的不少Jar包中,都是使用LinkedHashMap实现LRUCache。
下面的代码来自mysql-connector-java-5.1.18-bin.jar

package com.mysql.jdbc.util;

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache extends LinkedHashMap
{

    public LRUCache(int maxSize)
    {
        super(maxSize, 0.75F, true);
        maxElements = maxSize;
    }

    protected boolean removeEldestEntry(java.util.Map.Entry eldest)
    {
        return size() > maxElements;
    }

    private static final long serialVersionUID = 1L;
    protected int maxElements;
}

  

不过LeetCode的OJ确定不支持这样实现,上面的代码修改后提交,提示 Comoile Error 。

(2)使用双向链表实现

JDK中,LinkedHashMap是经过继承HashMap,维护一个双向链表实现,

当某个Cache位置被命中,经过调整链表的指向将该位置调整到头位置,新加入的内容直接放在链表头,在屡次进行Cache操做后,最近使用的Cache就会向链表头部移动,链表尾部就是命中次数最少,最久未使用的Cache。
空间充满时,移除尾部的数据就能够了。有几点须要注意,一个是Key不存在的状况,一个是缓存设计要求Key惟一。

下面使用双向链表实现LRU Cache,主要是维护一个缓存设定容量,当前容量,以及双向链表的头尾节点,方便移动和删除。

import java.util.HashMap; /** * 近期最少使用算法 设计缓存 */ public class LRUCache { private int cacheSize;//缓存容量 private int currentSize;//当前容量 private HashMap<Object, CacheNode> nodes;//缓存容器 private CacheNode head;//链表头 private CacheNode last;//链表尾 class CacheNode{ CacheNode prev;//前一节点 CacheNode next;//后一节点 int value;// int key;//  CacheNode() { } } //初始化缓存 public LRUCache(int capacity) { currentSize=0; cacheSize=capacity; nodes=new HashMap<Object, CacheNode>(capacity); } public Integer get(int key) { CacheNode node = nodes.get(key); if (node != null) { move(node); return node.value; } else { return -1;//error code  } } public void set(int key, int value) { CacheNode node = nodes.get(key); //重复Key if(node!=null){ node.value=value; move(node); nodes.put(key, node); }else {//key未重复,正常流程 node =new CacheNode(); if(currentSize>=cacheSize){ if (last != null){//缓存已满,进行淘汰  nodes.remove(last.key);} removeLast();//移除链表尾部并后移  }else{ currentSize++; } node.key=key; node.value=value; move(node); nodes.put(key, node); } } //移动链表节点至头部 private void move(CacheNode cacheNode){ if(cacheNode==head) return; //连接先后节点 if(cacheNode.prev!=null) cacheNode.prev.next=cacheNode.next; if(cacheNode.next!=null) cacheNode.next.prev=cacheNode.prev; //头尾节点 if (last == cacheNode) last = cacheNode.prev; if (head != null) { cacheNode.next = head; head.prev = cacheNode; } //移动后的链表 head = cacheNode; cacheNode.prev = null; //节点惟一的状况 if (last == null) last = head; } //移除指定缓存 public void remove(int key){ CacheNode cacheNode = nodes.get(key); if (cacheNode != null) { if (cacheNode.prev != null) { cacheNode.prev.next = cacheNode.next; } if (cacheNode.next != null) { cacheNode.next.prev = cacheNode.prev; } if (last == cacheNode) last = cacheNode.prev; if (head == cacheNode) head = cacheNode.next; } } //删除尾部的结点,即去除最近最久未使用数据 private void removeLast(){ if(last!=null){ if(last.prev!=null){ last.prev.next=null; }else{//空间大小为1的状况 head = null; } last = last.prev; } } public void clear() { head = null; last = null; } //测试用例 // public static void main(String[] args){ // LRUCache lCache=new LRUCache(2); // lCache.set(2, 1); // lCache.set(1, 1); // lCache.set(2, 3); // lCache.set(4, 1); // System.out.println(lCache.get(1)); // System.out.println(lCache.get(2)); // // }  }
相关文章
相关标签/搜索