最近最久未使用 如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小
1.用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。
2.利用一个链表来实现,每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。
3.利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。
对于第一种方法, 需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。对于第二种方法,链表在定位数据的时候时间复杂度为O(n)。所以在一般使用第三种方式来是实现LRU算法。
实现方案
4.使用LinkedHashMap实现
LinkedHashMap底层就是用的HashMap加双链表实现的,而且本身已经实现了按照访问顺序的存储。此外,LinkedHashMap中本身就实现了一个方法removeEldestEntry用于判断是否需要移除最不常读取的数,方法默认是直接返回false,不会移除元素,所以需要重写该方法。即当缓存满后就移除最不常用的数。
class LRUCache extends LinkedHashMap<Integer, Integer>{ private int capacity; public LRUCache(int capacity) { super(capacity, 0.75F, true); this.capacity = capacity; } public int get(int key) { return super.getOrDefault(key, -1); } public void put(int key, int value) { super.put(key, value); } @Override protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) { return size() > capacity; } }
class LRUCache { // 最大容量 int cap; // LUR的关键:哈希链表 LinkedHashMap<Integer,Integer> cache = new LinkedHashMap<>(); public LRUCache(int capacity) { this.cap = capacity; } public int get(int key) { if (!cache.containsKey(key)){ return -1; } // 调用了,所以把key置为最近使用 makeRencently(key); return cache.get(key); } public void put(int key, int value) { // 如果已经包含key,先修改key的值,再将它变为最近使用 if(cache.containsKey(key)){ // 修改key的值(因为key的唯一性,放入的时候回自动覆盖原来的value) cache.put(key,value); // 再将它变为最近使用 makeRencently(key); return; } // 如果超过了储存量,则删除头节点(因为实现的是尾插入,所以头结点就是最久没有用的,删去) if (cache.size() >= this.cap){ // 获取头结点 // Map中所有的键存入到set集合中。因为set具备迭代器。所有可以迭代方式取出所有的键 // 使用.iterator()迭代器后的指针其实指向的是第一个元素的上方,即指向一个空 // .next()指针下移一位,指向头节点。hasNext方法的,判断下一个元素的有无,并不移动指针 int oldestKey = cache.keySet().iterator().next(); //删去头结点 cache.remove(oldestKey); } // 放入,将新的key添加到链表尾部 cache.put(key,value); } // 将节点移到链表尾部,变为最近使用 public void makeRencently(int key){ // 获取key对应的值 int val = cache.get(key); // 删除key,重新插入队尾 cache.remove(key); cache.put(key,val); } }
注意、以上都不是面试官想要看到的,需要自己实现双向链表+hashmap的映射关系。
只需要三步:
1.构建Node节点
2.构建双向链表
3.构建hashmap映射
一、Node节点
// 一、节点类 class Node { public int key, val; public Node next, prev; //下一个和前面一个 public Node (int key, int val) { this.key = key; this.val = val; } } // 二、双端链表 class DoubleLinked { private Node head, tail;//头结点和尾结点 private int size;//链表的元素数目 public DoubleLinked() { head = new Node(0, 0); tail = new Node(0, 0); head.next = tail; tail.prev = head; size = 0; } // 在头部插入node结点 public void addFirst(Node x) { x.prev = head; x.next = head.next; head.next.prev = x; head.next = x; size++; } // 移除指定结点 public void remove(Node x) { x.prev.next = x.next; x.next.prev = x.prev; size--; } // 删除链表的第一个结点,并返回该结点 public Node removeLast() { if(head.next == tail) return null;//返回空 Node last = tail.prev; remove(last);//删除尾结点; return last; } public int size() { return size; } } // 三、构造hashMap映射 class LRUCache { HashMap<Integer, Node> map; DoubleLinked linked; int cap; //容量 public LRUCache(int capacity) { map = new HashMap<>(); linked = new DoubleLinked(); this.cap = capacity; } public int get(int key) { if(!map.containsKey(key)) return -1; int val = map.get(key).val; put(key, val);//放入头结点 return val; } public void put(int key, int value) { Node x = new Node(key, value); if(map.containsKey(key)) { linked.remove(map.get(key));//移除结点 linked.addFirst(x); map.put(key, x); }else { if(cap == cache.size()) { Node last = linked.removeLast(); map.remove(last.key); } linked.addFirst(x); map.put(key, x); } } }