运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
1.手写双向链表+使用map经典解法
class LRUCache {
/**
* 双向链表的节点
*/
static class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int key, int value) {
this.key = key;
this.value = value;
}
}
//lru的缓存
private final Map cache = new HashMap<>();
//双向链表的长度
private int size;
//lru的容量
private final int capacity;
//双向链表的前驱节点指针和后继节点指针
private final DLinkedNode head;
private final DLinkedNode tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
//使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
moveToHead(node);
return node.value;
}
//获取过的节点移动到链表的头部位置,并删除链表尾部的节点
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
//节点添加到头部
private void addToHead(DLinkedNode node) {
node.prev=head;
node.next=head.next;
head.next.prev=node;
head.next=node;
}
private void removeNode(DLinkedNode node) {
node.prev.next=node.next;
node.next.prev=node.prev;
}
public void put(int key, int value) {
DLinkedNode node=cache.get(key);
if (node==null) {
DLinkedNode newNode = new DLinkedNode(key, value);
cache.put(key, newNode);
//新添加的节点放在头部
addToHead(newNode);
++size;
if(size > capacity){
DLinkedNode tail=removeTail();
cache.remove(tail.key);
--size;
}
}else {
node.value=value;
moveToHead(node);
}
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);;
return res;
}
}
2.手写哈希表的解法
public class LRUCache {
private MyLinkedHashMap map;
public LRUCache(int capacity) {
map = new MyLinkedHashMap(capacity);
}
public int get(int key) {
return map.getOrDefault(key, -1);
}
public void put(int key, int value) {
map.put(key, value);
}
private class MyLinkedHashMap {
private final Entry[] table;
int modCount;
int size;
class Entry {
int hash;
K key;
V value;
Entry next;
Entry before, after;
public Entry() {
}
public Entry(int hash, K key, V value, Entry node) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = node;
}
}
Entry head;
Entry tail;
public MyLinkedHashMap(int initialCapacity) {
this.table = new Entry[initialCapacity];
modCount = initialCapacity;
size=0;
}
final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Entry newNode( K key, V value,Integer hash,Entry next) {
Entry p= new Entry( hash,key, value,next);
linkNodeLast(p);
return p;
}
private void linkNodeLast(Entry p){
Entry last=tail;
tail=p;
if(last==null){
head=p;
}else{
p.before=last;
last.after=p;
}
}
public V get(Object key) {
Entry e;
if ((e = getNode(hash(key), key)) == null)
return null;
afterNodeAccess(e);
return e.value;
}
Entry getNode(int hash, Object key) {
Entry[] tab; Entry first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
public V put(K key, V value) {
Integer hash = hash(key);
Entry p;
int n, i;
n = modCount;
if ((p = table[i = (n - 1) & hash]) == null) {
table[i] = newNode(key,value,hash,null);
size++;
} else {
Entry e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(key, value, hash,null);
size++;
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
afterNodeInsertion();
return null;
}
private void afterNodeInsertion() {
Entry first;
if ( (first = head) != null && removeEldestEntry()) {
K key = first.key;
removeNode(hash(key), key,null, false, true);
}
}
Entry removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Entry[] tab; Entry p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Entry node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node == p)
tab[index] = node.next;
else
p.next = node.next;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
//从链表将该元素删除
private void afterNodeRemoval(Entry e) {
Entry p =e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
private boolean removeEldestEntry() {
return size>modCount;
}
private void afterNodeAccess(Entry e) {
Entry last;
if ((last = tail) != e) {
Entry p =e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
}
}
public V getOrDefault(Object key, V defaultValue) {
V value;
if ((value = get(key)) == null) {
return defaultValue;
}
return value;
}
}
public static void main(String[] args) {
LRUCache cache = new LRUCache(2);
cache.put(1, 1);
cache.put(2, 2);
int res1 = cache.get(1);
System.out.println(res1);
cache.put(3, 3);
int res2 = cache.get(2);
System.out.println(res2);
int res3 = cache.get(3);
System.out.println(res3);
cache.put(4, 4);
int res4 = cache.get(1);
System.out.println(res4);
int res5 = cache.get(3);
System.out.println(res5);
int res6 = cache.get(4);
System.out.println(res6);
}
}