LRU缓存机制(LeetCode.146)

下面是2020年9月16日面试遇到的一道真实面试题。

题目

截图自LeetCode.146LRU缓存机制

解析

LRU(Least Recently Used)最近最少使用。在O(1)时间复杂度完成获取和写入数据两个操作。

  • 获取数据:定位到数据
  • 写入数据:获取数据并且更新;或者如果仍有缓存直接写入;如果缓存不够需要清理原有的最近最少使用的数据后,写入新数据。

上面两个数据操作,O(1)时间复杂度查找到数据可以使用map数据结构,除此之外还有可能会清理原有的最近最少使用的数据,这样在数据保存的基础上还需要记录数据访问的先后顺序,这个顺序可以使用链表实现。那么,我们定义的数据结构就应该兼有map和list两种属性。map可以直接复用JDK中的HashMap,然后在这个基础上,让HashMap中节点具有双向链表的属性,链表中节点按照访问时间早晚的顺序排列(表头数据是最近访问的,表尾数据是最近最少使用的)。
在内存中数据存储应该如下图所示:

内存中数据存储结构

使用JDK中的HashMap,定义自己value的节点结构,增加具有双向链表的性质。关于双向链表可以参考Java的LinkedList源码解析。
如下定义LRUNode。

public class LRUNode{
    int key;
    int value;
    LRUNode prev;
    LRUNode next;

    public LRUNode(){}

    public LRUNode(int key, int value){
        this.key = key;
        this.value = value;
    }
}

代码

两个操作逻辑:

  • get操作:如果key不存在,返回-1;如果存在返回value,并且将这个key的LRUNode移动到链表的头部(moveToHead()函数)。
  • put操作:如果key存在,更新value,将LRUNode移动链表的头部(moveToHead()函数);如果key不存在,判断当前缓存是否已满。如果缓存未满,直接map中放入新数据,并且新数据要连接到链表的头部(addToHead()函数);如果缓存已满,删除链表的尾部节点(最近最少使用的)(removeTailNode()函数),放入新数据并且连接到链表的头部(addToHead()函数)。
public class LRUCache {
    //缓存容量
    private int capacity;
    //双向链表的头尾指针
    private LRUNode head, tail;
    //JDK的map作为存储
    private Map cache;
    //缓存中当前实际的数据量
    private int size;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        cache = new HashMap<>();
        //定义头指针
        head = tail = new LRUNode();
    }

    //get操作
    public int get(int key) {
        LRUNode node = cache.get(key);
        //如果key不存在,返回-1
        if (null == node){
            return -1;
        }
        //如果存在返回value,并且将这个key的LRUNode移动链表的头部
        moveToHead(node);
        return node.value;
    }

    //put操作
    public void put(int key, int value) {
        LRUNode node = cache.get(key);
        //如果key存在
        if (null != node){
            //更新value
            node.value = value;
            //将LRUNode移动到链表的头部
            moveToHead(node);
            return;
        }
        //如果key不存在
        //缓存未满
        if (size < capacity){
            size++;
        } else {//缓存已满,删除链表的尾部节点
            removeTailNode();
        }
        LRUNode newNode = new LRUNode(key, value);
        //放入新数据
        cache.put(key, newNode);
        //新数据要连接到链表的头部
        addToHead(newNode);
    }

    //删除链表的尾部节点
    private void removeTailNode(){
        if (head == tail){
            return;
        }
        int key = tail.key;
        tail = tail.prev;
        tail.next = null;
        cache.remove(key);
    }

    //新数据连接到链表的头部
    private void addToHead(LRUNode node){
        if (head == tail){
            head.next = node;
            node.prev = head;
            tail = node;
            return;
        }
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
        node.prev = head;
    }

    //移动节点到链表的头部
    private void moveToHead(LRUNode node){
        if (node.prev == head){
            return;
        }
        LRUNode prev = node.prev;
        if (node == tail){
            prev.next = null;
            tail = prev;
        } else {
            prev.next = node.next;
            node.next.prev = prev;
        }
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
        node.prev = head;
    }
}

你可能感兴趣的:(LRU缓存机制(LeetCode.146))