LRU缓存机制
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get
和 写入数据 put
。
获取数据 get(key)
- 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value)
- 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
示例:
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
方法一:双链表+HashMap
要用map查找,key自然存cache的key,要找到链表,维持顺序,value应该为Node
Node中要存cache的value,移除链表最后元素时也要将它从map中删除,所以Node需要存key
为了方便,添加了头结点和尾结点
class LRUCache {
private int size;
private int capacity;
private class Node {
int key;
int value;
Node pre;
Node next;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private Node head = new Node(0, 0);
private Node tail = new Node(0, 0);
private Map<Integer, Node> map = new HashMap<>();
private void moveToHead(Node node) {
Node pre = node.pre;
Node next = node.next;
pre.next = next;
next.pre = pre;
Node first = head.next;
head.next = node;
node.pre = head;
node.next = first;
first.pre = node;
}
private void insertToHead(Node node) {
Node first = head.next;
head.next = node;
node.pre = head;
node.next = first;
first.pre = node;
}
private void removeLast() {
Node preOfLast = tail.pre.pre;
preOfLast.next = tail;
tail.pre = preOfLast;
}
public LRUCache(int capacity) {//>=1
this.capacity = capacity;
head.next = tail;
tail.pre = head;
}
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.value = value;
moveToHead(node);
} else {
if (size >= capacity) {
map.remove(tail.pre.key);
removeLast();
}
node = new Node(key, value);
insertToHead(node);
map.put(key, node);
size++;
}
}
}
LinkedList
class LRUCache {
private int capacity;
//accessOrder=true
private Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>(capacity,0.75f,true){
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return this.size() > capacity;
}
};
public LRUCache(int capacity) {
this.capacity = capacity;
}
public int get(int key) {
Integer val = map.get(key);
return val == null ? -1 : val;
}
public void put(int key, int value) {
map.put(key,value);
}
}
LFU缓存
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get
和 put
。
-
get(key)
- 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。 -
put(key, value)
- 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除最久未使用的键。
「项的使用次数」就是自插入该项以来对其调用 get
和 put
函数的次数之和。使用次数会在对应项被移除后置为 0 。
示例:
LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 去除 key 2
cache.get(2); // 返回 -1 (未找到key 2)
cache.get(3); // 返回 3
cache.put(4, 4); // 去除 key 1
cache.get(1); // 返回 -1 (未找到 key 1)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
方法一:HashMap+TreeSet
从map实现找找,value存Node
Set用来删除元素
get()、 put():如果已经存在,先从set中删除,修改后再加入
class LFUCache {
private int size;
private int capacity;
private class Node implements Comparable<Node> {
int key;
int value;
int count;
long time;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
@Override
public int compareTo(Node o) {
if (this.count != o.count) {
return this.count - o.count;
}
return (int) (this.time - o.time);
}
}
private Map<Integer, Node> map = new HashMap<>();
private TreeSet<Node> set = new TreeSet<>();
public LFUCache(int capacity) {//>=1
this.capacity = capacity;
}
public int get(int key) {
if (capacity == 0) {
return -1;
}
Node node = map.get(key);
if (node == null) {
return -1;
}
set.remove(node);
node.count++;
node.time = System.nanoTime();//不能用currentTime,不精确,时间可能重复
set.add(node);
return node.value;
}
public void put(int key, int value) {
if (capacity == 0) {
return;
}
Node node = map.get(key);
if (node != null) {
set.remove(node);
node.value = value;
node.count++;
node.time = System.nanoTime();
set.add(node);
} else {
if (size >= capacity) {
map.remove(set.first().key);
set.remove(set.first());
size--;
}
node = new Node(key, value);
node.count = 1;
node.time = System.nanoTime();
map.put(key, node);
set.add(node);
size++;
}
}
}
时间复杂度O(logn)
方法二:双Hash表
class LFUCache {
private class Node {
int key;
int value;
int count;
Node next;
Node pre;
Node(int key, int value, int count) {
this.key = key;
this.value = value;
this.count = count;
}
}
private class DoubleLinkedList {
Node head = new Node(0, 0, 0);
Node tail = new Node(0, 0, 0);
DoubleLinkedList() {
head.next = tail;
tail.pre = head;
}
void unlink(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
if (head.next == tail && node.count == minFre) {//当前频率的链表空了并且处于最小频率,更新最小频率
minFre++;
}
}
void insertFirst(Node node) {
node.next = head.next;
head.next.pre = node;
node.pre = head;
head.next = node;
}
Node removeLast() {
Node last = tail.pre;
tail.pre.pre.next = tail;
tail.pre = tail.pre.pre;
return last;
}
}
private int size;
private int capacity;
private int minFre;//访问最小的频率,在删除时会用到
public LFUCache(int capacity) {
this.capacity = capacity;
}
private Map<Integer, Node> keyMap = new HashMap<>();
private Map<Integer, DoubleLinkedList> freMap = new HashMap<>();//key:访问次数
public int get(int key) {
if (capacity == 0) {
return -1;
}
Node node = keyMap.get(key);
if (node == null) {
return -1;
}
DoubleLinkedList doubleLinkedList = freMap.get(node.count);//获得对应频率的链表
doubleLinkedList.unlink(node);//从原链表删除
node.count++;
doubleLinkedList = freMap.get(node.count);//频率+1的链表
if (doubleLinkedList == null) {//还不存在先初始化
doubleLinkedList = new DoubleLinkedList();
freMap.put(node.count, doubleLinkedList);
}
doubleLinkedList.insertFirst(node);
return node.value;
}
public void put(int key, int value) {
if (capacity == 0) {
return;
}
Node node = keyMap.get(key);
if (node != null) {
DoubleLinkedList doubleLinkedList = freMap.get(node.count);
doubleLinkedList.unlink(node);
node.value = value;
node.count++;
doubleLinkedList = freMap.get(node.count);
if (doubleLinkedList == null) {//还不存在先初始化
doubleLinkedList = new DoubleLinkedList();
freMap.put(node.count, doubleLinkedList);
}
doubleLinkedList.insertFirst(node);
} else {
if (size >= capacity) {
Node last = freMap.get(minFre).removeLast();
size--;
keyMap.remove(last.key);
}
node = new Node(key, value, 1);
DoubleLinkedList doubleLinkedList = freMap.get(1);
if (doubleLinkedList == null) {
doubleLinkedList = new DoubleLinkedList();
freMap.put(node.count, doubleLinkedList);
}
doubleLinkedList.insertFirst(node);
keyMap.put(key, node);
size++;
minFre = 1;
}
}
}