一。LRU算法简介
LRU(Least Recently Used)最近最久未使用算法
常见应用场景:内存管理中的页面置换算法、缓存淘汰中的淘汰策略等
二。实现理论
底层结构:双向链表 + HashMap ,双向链表由特定的哈希节点组成。
(1)访问节点时,将其从原来位置删除,插入到双向链表头部;
(2)更新节点时,先删除原有缓存数据(即原有节点),然后更新map映射,再将更新值作为节点插入链表头;更新后,判断容量是否超过最大内存使用量
(3)超过则执行淘汰;淘汰即删除双向链表最后一个节点,同时删除map中的映射
(4)LRU实现中有频繁的查找节点并删除,为节省时间(链表查找目标节点需要遍历),使用HashMap保存键-节点映射关系,O(1)的查找+O(1)的删除
(5)LRU实现中,要频繁的在头部插入,以及在尾部删除;因此,需要定义head、tail两个节点,方便操作
三。代码
1 package com.xl.Base; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 6 /** 7 * 最近最久未使用淘汰策略 8 * 基于 双向链表 + 哈希表组成,其中双向链表由哈希链表节点构成 9 * 封装为 LRU(K, V) 10 * 对外提供 get(K)访问数据、put(K, V)更新数据、Iterator()遍历数据 11 */ 12 public class LRU<K, V> implements Iterable<K>{ 13 14 private Node head; 15 private Node tail; 16 //记录K-Node映射,便于快速查找目标数据对应节点 17 private HashMap<K, Node> map; 18 private int maxSize; 19 20 //哈希链表节点类 Node 21 private class Node{ 22 Node pre; 23 Node next; 24 K k; 25 V v; 26 27 //Node对外提供构造方法 28 public Node(K k, V v) { 29 this.k = k; 30 this.v = v; 31 } 32 } 33 34 //初始化时必须传入最大可用内存容量 35 public LRU(int maxSize){ 36 this.maxSize = maxSize; 37 //HashMap初始容量设置为 maxSize * 4/3,即达到最大可用内存时,HashMap也不会自动扩容浪费空间 38 this.map = new HashMap<>(maxSize * 4 / 3); 39 40 head.next = tail; 41 tail.pre = head; 42 } 43 44 //获取指定数据 45 private V get(K key) { 46 //判断是否存在对应数据 47 if(!map.containsKey(key)) { 48 return null; 49 } 50 51 //最新访问的数据移动到链表头 52 Node node = map.get(key); 53 remove(node); 54 addFirst(node); 55 return node.v; 56 } 57 58 //更新旧数据或添加数据 59 private void put(K key, V value) { 60 //若存在旧数据则删除 61 if(map.containsKey(key)) { 62 Node node = map.get(key); 63 remove(node); 64 } 65 66 //新数据对应节点插入链表头 67 Node node = new Node(key, value); 68 map.put(key, node); 69 addFirst(node); 70 71 //判断是否需要淘汰数据 72 if(map.size() > maxSize) { 73 removeLast(); 74 //数据节点淘汰后,同时删除map中的映射 75 map.remove(node.k); 76 } 77 } 78 79 //将指定节点插入链表头 80 private void addFirst(Node node) { 81 Node next = head.next; 82 83 head.next = node; 84 node.pre = head; 85 86 node.next = next; 87 next.pre = node; 88 } 89 90 //从链表中删除指定节点 91 private void remove(Node node) { 92 Node pre = node.pre; 93 Node next = node.next; 94 95 pre.next = next; 96 next.pre = pre; 97 98 node.next = null; 99 node.pre = null; 100 } 101 102 //淘汰数据 103 private Node removeLast() { 104 //找到最近最久未使用的数据所对应节点 105 Node node = tail.pre; 106 107 //淘汰该节点 108 remove(node); 109 110 return node; 111 } 112 113 //通过迭代器遍历所有数据对应键 114 @Override 115 public Iterator<K> iterator() { 116 return new Iterator<K>() { 117 118 private Node cur = head.next; 119 120 @Override 121 public boolean hasNext() { 122 return cur != tail; 123 } 124 125 @Override 126 public K next() { 127 Node node = cur; 128 cur = cur.next; 129 return node.k; 130 } 131 132 }; 133 } 134 135 }