Java实现LRU算法

一。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 }

 

posted @ 2019-07-15 21:13  埃克斯诶尔  阅读(2483)  评论(0编辑  收藏  举报