题目
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) — 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) — 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
进阶: 你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1(未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回-1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
解题思路—双向链表+哈希表:使用双向链表与哈希表的结合,可以实现在O(1)的时间复杂度内完成LRU缓存机制。构建带有表头和表尾的双链表作为LRU索引表,并且用HashMap作为数据表来存储所有链表节点,添加或更新的节点都放在链表头,若HashMap中的个数达到容量上限时,删除链表的最后一个节点。获取数据时,直接从HashMap中get对应的key值value即可。
解题思路—使用LinkedHashMap:LinkedHashMap是Map接口的哈希表和双向链表的结合,特别适合实现LRU缓存机制。
LinkedHashMap中有一个带参的构造函数:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
其中:
- initialCapacity为HashMap中桶的数量(默认为16个);
- loadFactor为装载因子,用来判断HashMap中的桶有没有满,loadFactor的默认值为0.75f,计算装载因子的公式为 HashMap中存在的KV键值数量/capacity。当元素个数超16loadFactor也就是160.75=12时候,HashMap会先扩容2倍到32,接着还有可能会到64等。
- accessOrder定义了排序模式,boolean型,若双向链表的排序为访问顺序从少到多,则为true;若排序为插入顺序,则为false,也就是默认值。
LinkedHashMap还提供了removeEldestEntry(Map.Entry<K,V> eldest)方法,该方法可以提在每次添加新元素时移除最旧元素。方法返回默认为false,不移除旧元素。当返回值为true的时候,可以实现移除功能。方法如下:
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
所以,想要实现LRU缓存机制,只要新建一个带参的LinkedHashMap,同时Override函数removeEldestEntry,返回map.size>capacity,即当前map中的容量大于设定上限时,实现最旧元素移除功能。
Java解题中提供了两种代码实现方式,一个是新建一个class,还有一个是在实例化对象的时候override。
Java解题—双向链表+哈希表
import java.util.HashMap;
import java.util.Map;
class LRUCache {
// 构建双链表节点
class Node{
int key;
int value;
Node pre;
Node next;
public Node(int key, int value){
this.key = key;
this.value = value;
}
}
private int capacity;
private Node first;
private Node last;
private Map<Integer, Node> map;
public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>(capacity);
}
public int get(int key) {
Node node = map.get(key);
if(node==null) return -1;
movetoHead(node);
return node.value;
}
public void put(int key, int value) {
Node node = map.get(key);
if(node==null){
node = new Node(key, value);
// 如果当前map中存放了capacity个
// 需要删除最后一个再加入
if(map.size()==this.capacity)
removeLast();
addtoHead(node);
map.put(key, node);
}else{
node.value = value;
movetoHead(node);
}
}
// 把当前节点调置链表的第一个
public void movetoHead(Node node){
if(node==first)
return;
else if(node==last){
last.pre.next = null;
last = last.pre;
}else{
node.pre.next = node.next;
node.next.pre = node.pre;
}
node.pre = first.pre;
node.next = first;
first.pre = node;
first = node;
}
// 移除链表中最后一个结点
public void removeLast(){
map.remove(last.key);
Node prenode = last.pre;
if(prenode!=null)
prenode.next = null;
last = prenode;
}
// 将新节点作为链表的头节点
public void addtoHead(Node node){
if(map.isEmpty()){
first = node;
last = node;
}else{
node.next = first;
first.pre = node;
first = node;
}
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
Java解题—使用LinkedHashMap-1
import java.util.LinkedHashMap;
import java.util.Map.Entry;
class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V>{
// 缓存容量
private int capacity;
// 带参构造器
public LRULinkedHashMap(int capacity){
// accessOrder设为true,代表开启LRU缓存,该哈希映射的迭代顺序就是最后访问其条目的顺序
// 0.75为LinkedHashMap默认装载因子大小
super(capacity, 0.75f, true);
this.capacity = capacity;
}
// 当插入后的size大于capacity时,返回ture,删除链表的顶端元素
// macos的idea中,override快捷键为command+o,可以选择能够重载的函数
@Override
protected boolean removeEldestEntry(Entry<K, V> eldest) {
return this.size() > this.capacity;
}
}
class LRUCache {
private LRULinkedHashMap<Integer, Integer> LRUMap; // 按访问顺序保存
public LRUCache(int capacity) {
this.LRUMap = new LRULinkedHashMap(capacity);
}
public int get(int key) {
Integer value = LRUMap.get(key);
if(value==null) return -1;
return value;
}
public void put(int key, int value) {
LRUMap.put(key, value);
}
}
Java解题—使用LinkedHashMap-2
import java.util.LinkedHashMap;
import java.util.Map.Entry;
class LRUCache {
private LinkedHashMap<Integer, Integer> map;
private int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true){
@Override
protected boolean removeEldestEntry(Entry<Integer, Integer> eldest) {
return this.size()> capacity;
}
};
}
public int get(int key) {
Integer value = this.map.get(key);
if(value==null) return -1;
return value;
}
public void put(int key, int value) {
this.map.put(key, value);
}
}