LRU CaChe(内存替换算法)
2025-06-24 12:40:35
来源:新华网
六、LURCache
0、LUR Cache概念
LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache?狭义的Cache指的是位于CPU和主存间的快速RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。广义上的Cache指的是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache,内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时,就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实, LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。
2、LRU Cache的实现
实现LRU Cache的方法和思路很多,但是要保持高效实现O(1)的put和get,那么使用双向链表和哈希表的搭配是最高效和经典的。使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,使用哈希表是因为哈希表的增删查改也是O(1)。
3、JDK中类似LRUCahe的数据结构LinkedHashMap
参数说明:
- initialCapacity 初始容量大小:使用无参构造方法时,此值默认是16。
- loadFactor 加载因子:使用无参构造方法时,此值默认是 0.75f。
- accessOrder: false表示基于插入顺序;true表示基于访问顺序。
publicstaticvoidmain(String[]args){ // 当accessOrder为false时:Map<String,String>map =newLinkedHashMap<>(16,0.75f,false);map.put("1","a");map.put("2","b");map.put("4","e");map.put("3","c");System.out.println(map);}// 输出结果:// { 1=a, 2=b, 4=e, 3=c}// 以上结果按照插入顺序进行打印
publicstaticvoidmain(String[]args){ Mapmap =newLinkedHashMap<>(16,0.75f,true);map.put("1","a");map.put("2","b");map.put("4","e");map.put("3","c");map.get("1");map.get("2");System.out.println(map);}/*输出结果:{ 4=e, 3=c, 1=a, 2=b}每次使用get方法,访问数据后,会把数据放到当前双向链表的最后。当accessOrder为true时,get方法和put方法都会调用recordAccess方法使得最近使用的Entry移到双向链表的末尾;当accessOrder为默认值false时,从源码中可以看出recordAccess方法什么也不会做。*/
4、LRUCache结构的特点
双向链表的头节点是最近最少使用的元素,尾节点是最近最长用的节点。
LRUCache(Least Recently Used Cache)是一种常用的缓存淘汰策略,它的主要特点如下:
- 容量限制:LRUCache有固定的容量上限,当缓存满时需要淘汰最久未使用的数据。
- 快速访问:LRUCache需要能够快速地查找、插入和删除缓存项,通常使用哈希表(字典)和双向链表的组合实现。
- 访问顺序跟踪:LRUCache需要记录每个缓存项的访问顺序,以便快速找到最久未使用的项。
- 最近最少使用:当缓存满时,LRUCache会淘汰最近最少被访问的缓存项。这样可以保留最有价值的数据。
- 时间复杂度:基于哈希表和双向链表的LRUCache实现,可以达到平均时间复杂度O(1)的增删查操作。
典型的LRUCache实现如下:
- 使用一个哈希表(字典)存储键值对,便于快速查找。
- 使用一个双向链表维护访问顺序,最近访问的节点放在链表头部。
- 当缓存已满时,将链表尾部(最久未使用)的节点移除。
- 当访问一个缓存项时,将其移动到链表头部。
- 当添加一个新缓存项时,将其加到链表头部。
这种结构可以高效地实现LRU缓存的所有操作,是LRU缓存广泛使用的基础。
5、手搓LRUCaChe
importjava.util.LinkedHashMap;importjava.util.Map;publicclassLRUCacheextendsLinkedHashMap<Integer,Integer>{ publicintcapacity;publicLRUCache(intcapacity){ //这个的true 代表 基于访问顺序super(capacity,0.75F,true);this.capacity =capacity;}@OverridepublicIntegerget(Objectkey){ returnsuper.getOrDefault(key,-1);}@OverridepublicIntegerput(Integerkey,Integervalue){ returnsuper.put(key,value);}@OverrideprotectedbooleanremoveEldestEntry(Map.Entry<Integer,Integer>eldest){ returnsize()>capacity;}publicstaticvoidmain(String[]args){ LRUCachelruCache =newLRUCache(3);lruCache.put(100,10);lruCache.put(110,11);lruCache.put(120,12);System.out.println(lruCache);System.out.println("获取元素");System.out.println(lruCache.get(110));System.out.println(lruCache);System.out.println(lruCache.get(100));System.out.println(lruCache);System.out.println("存放元素,会删除头节点,因为头节点是最近最少使用的: ");lruCache.put(999,99);System.out.println(lruCache);}publicstaticvoidmain3(String[]args){ LinkedHashMap<String,Integer>linkedHashMap =newLinkedHashMap<>(16,0.7f,true);linkedHashMap.put("高博",10);linkedHashMap.put("abcd",11);linkedHashMap.put("hello",12);System.out.println(linkedHashMap);System.out.println("获取元素");System.out.println(linkedHashMap.get("abcd"));System.out.println(linkedHashMap);System.out.println(linkedHashMap.get("高博"));System.out.println(linkedHashMap);}//是基于插入顺序publicstaticvoidmain1(String[]args){ LinkedHashMap<String,Integer>linkedHashMap =newLinkedHashMap<>(16,0.7f,false);linkedHashMap.put("高博",10);linkedHashMap.put("abcd",11);linkedHashMap.put("hello",12);System.out.println(linkedHashMap);System.out.println("获取元素");System.out.println(linkedHashMap.get("abcd"));System.out.println(linkedHashMap);}}
importjava.util.HashMap;importjava.util.Map;/** * @Author 12629 * @Description: */publicclassMyLRUCache{ staticclassDLinkNode{ publicintkey;publicintval;publicDLinkNodeprev;publicDLinkNodenext;publicDLinkNode(){ }publicDLinkNode(intkey,intval){ this.key =key;this.val =val;}@OverridepublicStringtoString(){ return"{ key="+key +", val="+val+"} ";}}publicDLinkNodehead;//双向链表的头节点publicDLinkNodetail;//双向链表的尾巴节点publicintusedSize;//代表当前双向链表当中 有效的数据个数publicMap<Integer,DLinkNode>cache;//定义一个mappublicintcapacity;//容量publicMyLRUCache(intcapacity){ this.head =newDLinkNode();this.tail =newDLinkNode();head.next =tail;tail.prev =head;cache =newHashMap<>();this.capacity =capacity;}/** * 存储元素 * 1. 查找当前的这个key 是不是存储过 * @param key * @param val */publicvoidput(intkey,intval){ //1. 查找当前的这个key 是不是存储过DLinkNodenode =cache.get(key);//2. 如果没有存储过if(node ==null){ //2.1 需要实例化一个节点DLinkNodedLinkNode =newDLinkNode(key,val);//2.2 存储到map当中一份cache.put(key,dLinkNode);//2.3 把该节点存储到链表的尾巴addToTail(dLinkNode);usedSize++;//2.4 检查当前双向链表的有效数据个数 是不是超过了capacityif(usedSize >capacity){ //2.5 超过了,就需要移除头部的节点DLinkNoderemNode =removeHead();//2.6 清楚cache当中的元素cache.remove(remNode.key);//2.7 usedSize--;usedSize--;}printNodes("put");}else{ //3. 如果存储过//3.1 更新这个key对应的valuenode.val =val;//3.2 然后将该节点,移动至尾巴处【因为这个是新插入的数据】moveToTail(node);}}/** * 移除当前节点到尾巴节点 * 逻辑:先删除 后添加到尾巴 * @param node */privatevoidmoveToTail(DLinkNodenode){ //1. 先删除这个节点removeNode(node);//2. 添加到尾巴节点addToTail(node);}/** * 删除指定节点 * @param node */privatevoidremoveNode(DLinkNodenode){ node.prev.next =node.next;node.next.prev =node.prev;}/** * 添加节点到链表的尾部 * @param node */privatevoidaddToTail(DLinkNodenode){ tail.prev.next =node;node.prev =tail.prev;node.next =tail;tail.prev =node;}privateDLinkNoderemoveHead(){ DLinkNodedel =head.next;head.next =del.next;del.next.prev =head;returndel;}/** * 访问当前的key * 逻辑:把你访问的节点 放到尾巴 * @param key * @return */publicintget(intkey){ DLinkNodenode =cache.get(key);if(node ==null){ return-1;}//把最近 最多使用的 放到了链表的尾巴moveToTail(node);printNodes("get ");returnnode.val;}publicvoidprintNodes(Stringstr){ System.out.println(str+": ");DLinkNodecur =head.next;while(cur !=tail){ System.out.print(cur);cur =cur.next;}System.out.println();}publicstaticvoidmain(String[]args){ MyLRUCachelruCache =newMyLRUCache(3);lruCache.put(100,10);lruCache.put(110,11);lruCache.put(120,12);System.out.println("获取元素");System.out.println(lruCache.get(110));System.out.println(lruCache.get(100));System.out.println("存放元素,会删除头节点,因为头节点是最近最少使用的: ");lruCache.put(999,99);}}