主播今天刷leetcode刷到146.LRU缓存,并对LinkedHashMap的源码展开了学习,给大家分享一下:
先简单介绍一下题目要求,所谓LRU就是一个简单的缓存,比如说存入了[1,1],[2,2],然后我根据键值获取对应的值并对其加1,获取了[2,2]那么就把[2,2]放到容器尾部,并且将值加1,容器内部由[1,1],[2,2]变为[2,3],[1,1],并且如果LRU是有大小的,如果超出大小在添加元素后时需要舍弃最不常使用的元素的,具体的要求大家可以去看leetcode官网上面。
容器最重要的两个性能就是存入和取出的时间复杂度,我们想把这个时间复杂度压缩到O(1),因此我们对于数据结构的选择是哈希表和双向链表,每次添加新元素就会把他放在队尾,老元素被访问也会被移动到队尾,哈希表保证取数据的速度,双向链表则是方便我们对数据进行移动操作,同时在添加新元素的时候时间复杂度也是O(1)因为是在仅队尾一侧进行添加的。
选择完数据结构,再给大家介绍一下Java为我们提供的集合类LinkedHashMap,他的底层数据结构是链表、红黑树加数组,就是HashMap的基础上按照一定规则维护了一个有序的链表,这个链表的顺序有两种,一种是根据我们插入元素的顺序,而另一种则是根据我们访问的顺序,我们可以通过在构造方法中通过boolean值进行选择,false是根据插入顺序,true是根据访问顺序。和我们的需求简直天作地设。
对于LinkedHashMap,对其进行数据的插入和删除时,会通过三个回调函数来进行链表的维护:
void afterNodeAccess(Node<K,V> p) :在开启根据访问顺序调整链表后,这个方法会在访问数据后,将被访问的数据移动到链表尾部;
void afterNodeRemoval(Node<K,V> p):其作用就是在删除元素之后,将元素从双向链表中删除,还是非常建议大家去看看这个函数的,很优美的方式在双向链表中删除节点;
void afterNodeInsertion(boolean evict) :在插入元素后,用回调函数判断是否需要删除一些元素(因为对于容器的大小有限制);
对于LinkedHashMap,它有两种构造函数:
java
/**
* //调用父类HashMap的构造方法。
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the default initial capacity (16) and load factor (0.75).
*/
public LinkedHashMap() {
super();
accessOrder = false;
}
// 这里的 accessOrder 默认是为false,如果要按读取顺序排序需要将其设为 true
// initialCapacity 代表 map 的 容量,loadFactor 代表加载因子 (默认即可)
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
一个默认的就是调用HashMap的构造方法同时调整为根据插入顺序维护链表,另一个是自己对大小,负载因子,链表维护模式。
对于LinkedHashMap中的关键方法afterNodeInsertion是这样的:
java
// 在插入一个新元素之后,如果是按插入顺序排序,即调用newNode()中的linkNodeLast()完成
// 如果是按照读取顺序排序,即调用afterNodeAccess()完成
// 那么这个方法是干嘛的呢,这个就是著名的 LRU 算法啦
// 在插入完成之后,需要回调函数判断是否需要移除某些元素!
// LinkedHashMap 函数部分源码
/**
* 插入新节点才会触发该方法,因为只有插入新节点才需要内存
* 根据 HashMap 的 putVal 方法, evict 一直是 true
* removeEldestEntry 方法表示移除规则, 在 LinkedHashMap 里一直返回 false
* 所以在 LinkedHashMap 里这个方法相当于什么都不做
*/
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// 根据条件判断是否移除最近最少被访问的节点
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
// 移除最近最少被访问条件之一,通过覆盖此方法可实现不同策略的缓存
// LinkedHashMap是默认返回false的,我们可以继承LinkedHashMap然后复写该方法即可
// 例如 LeetCode 第 146 题就是采用该种方法,直接 return size() > capacity;
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
在LinkedHashMap中的removeEldestEntry是默认返回false,也就是说它默认情况不会删除元素,如果我们想要自定义删除元素的规则那我们需要重写它就好了;
至此大致思路有了,选择用LinkedHashMap+调整链表维护策略+重写removeEldestEntry就好了
题目答案代码附上
java
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;
}
}
/**
* 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);
*/