赞
踩
LRU,即:最近最少使用淘汰算法(Least Recently Used)。LRU是淘汰最长时间没有被使用的数据。
LFU,即:最不经常使用淘汰算法(Least Frequently Used)。LFU是淘汰一段时间内,使用次数最少的数据。
举例:
数据2 1 2 1 2 3 4,加入4时内存超过阈值。
根据LRU会淘汰1,因为1距离想加入4的时间点(也就是淘汰时间点)最长。
根据LFU会淘汰3,因为加入4前的这段时间里,3的使用次数最少。
LRU算法实现:
LeetCode官方题解:https://leetcode-cn.com/problems/lru-cache/solution/lruhuan-cun-ji-zhi-by-leetcode-solution/
这里记录一下借助LinedHashMap的原有机制实现。
- class LRUCache extends LinkedHashMap<Integer,Integer>{
- private int capacity;
- public LRUCache(int capacity) {
- // LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
- 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);
- }
- protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest){
- return size() > capacity;
- }
- }
LinkedHashMap的底层数据结构和HashMap一样,采用数组+单向链表(JDK1.8之前),只是在节点Entry中增加了before和after变量,用于维护一个双向链表来保存LinkedHashMap的节点顺序。
当accessOrder为false时(默认情况),LinkedHashMap的节点顺序是插入顺序。而accessOrder为true之后,在访问结点时,会将相应结点移动到双向链表的尾部,所以头部的节点是最近最少使用的节点,节点都是唯一的,所以头部的节点其实也是最近未使用的节点。
LinkedHashMap:
- public V get(Object key) {
- Node<K,V> e;
- if ((e = getNode(hash(key), key)) == null)
- return null;
- if (accessOrder)
- afterNodeAccess(e);
- return e.value;
- }
put到removeEldestEntry的调用路径:
HashMap:
- public V put(K key, V value) {
- return putVal(hash(key), key, value, false, true);
- }
- final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
- boolean evict) {
- Node<K,V>[] tab; Node<K,V> p; int n, i;
- if ((tab = table) == null || (n = tab.length) == 0)
- n = (tab = resize()).length;
- if ((p = tab[i = (n - 1) & hash]) == null)
- tab[i] = newNode(hash, key, value, null);
- else {
- Node<K,V> e; K k;
- if (p.hash == hash &&
- ((k = p.key) == key || (key != null && key.equals(k))))
- e = p;
- else if (p instanceof TreeNode)
- e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
- else {
- for (int binCount = 0; ; ++binCount) {
- if ((e = p.next) == null) {
- p.next = newNode(hash, key, value, null);
- if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
- treeifyBin(tab, hash);
- 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;
- if (!onlyIfAbsent || oldValue == null)
- e.value = value;
- afterNodeAccess(e);
- return oldValue;
- }
- }
- ++modCount;
- if (++size > threshold)
- resize();
- afterNodeInsertion(evict);
- return null;
- }
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);
- }
- }
- protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
- return false;
- }
顺便说一下插入和访问时的节点顺序维护:
LinkedHashMap:
- void afterNodeAccess(Node<K,V> e) { // move node to last
- LinkedHashMap.Entry<K,V> last;
- if (accessOrder && (last = tail) != e) {
- LinkedHashMap.Entry<K,V> p =
- (LinkedHashMap.Entry<K,V>)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;
- ++modCount;
- }
- }
LFU:
简单暴力的解法是记录各节点访问频次,淘汰节点时选择频次最小的节点即可。高级的LeetCode官方题解看链接:平衡二叉树或者双哈希表https://leetcode-cn.com/problems/lfu-cache/solution/lfuhuan-cun-by-leetcode-solution/。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。