使用LinkedHashMap实现LRU算法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用LinkedHashMap实现LRU算法相关的知识,希望对你有一定的参考价值。
参考技术A LinkedHashMap是比HashMap多了一个链表的结构。与HashMap相比LinkedHashMap维护的是一个具有双重链表的HashMap,LinkedHashMap支持2中排序一种是插入排序,即插入是什么顺序,读出来的就是什么顺序。一种是使用排序,最近使用的会移至尾部例如 key1 key2 key3 key4,使用key3后为 key1 key2 key4 key3了。accessOrder为true表示使用顺序,false表示插入顺序。基于LinkedHashMap的使用顺序的特性,我们可以用来实现LRU算法(Mybatis的LRU算法也是这样实现的)
bigSize表示缓存最大容量,超过这个值最近最少使用的key,将会被移除。
测试
结果如下,当我们重新访问前3个值后,他们会被放到链表最后。前面的值会被移除。
手撸LRU算法(java实现)
正文在下面,先打个广告:
使用javaAPI
java 的LinkedHashMap可以实现LRU算法,LinkedHashMap 本身内部有一个触发条件则自动执行的方法:删除最老元素(最近最少使用的元素)。我们只需要通过参数控制数据排序逻辑和数据删除规则就行了。
LinkedHashMap源码中有这么一个注释:
<p>The @link #removeEldestEntry(Map.Entry) method may be overridden to
* impose a policy for removing stale mappings automatically when new mappings
* are added to the map.
可以看到 removeEldestEntry 方法可以用来删除过时的数据。不过他的默认实现是返回false,需要我们手动重写该方法。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest)
return false;
还有一个需要注意的是,LinkedHashMap默认是按照插入顺序排序的,很显然不能满足LRU算法的需求。LRU是希望按照访问顺序排序,最新访问的数据放到队头,老数据放到队尾,这样数据总量超过容量时,删除队尾的数据即可。
LinkedHashMap构造函数中有一个参数accessOrder,该参数控制了数据排序的规则。下面get方法中可以看到,有个if判断,如果accessOrder是true,则将当前访问的node移动到队尾last(LinkedHashMap是用last存储最新数据)
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder)
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
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;
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;
当插入数据时,就会通过removeEldestEntry方法的返回值决定是否需要删除老的数据。
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);
完整代码:
public interface ILru
void put(Object key, Object value);
void remove(String key);
Object get(String key);
public class JavaLRUCache implements ILru
private Map<Object, Object> map;
private final int capacity;
public JavaLRUCache(int capacity)
this.capacity = capacity;
map = new LinkedHashMap<Object, Object>(capacity, 0.75f, true)
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest)
return size() > capacity;
@Override
public String toString()
return super.toString();
;
@Override
public Object get(String key)
return map.get(key);
@Override
public void put(Object key, Object value)
map.put(key, value);
@Override
public void remove(String key)
map.remove(key);
@Override
public String toString()
return "JavaLRUCache" +
"map=" + map +
", capacity=" + capacity +
'';
纯手撸
纯手撸的话,其实和上面LinkedHashMap原理一样。
先定义node
/**
* @author liubenlog
* @className Node
* @description map 中的V,设置为一个对象,主要是为了记录前后指针,已达到 0(1)的时间复杂度
* @date 2020/10/22 16:02
*/
@Data
public class Node
private Object key;
private Object value;
private Node pre;
private Node next;
public Node(Object key, Object value)
this.key=key;
this.value=value;
@Override
public String toString()
return key.toString() + "=" + value.toString();
再定义链表,包含LRU的核心算法
/**
* @author liubenlog
* @className MyList
* @description 双向链表. 我这里规定head是最新的数据
* @date 2020/10/22 16:03
*/
public class MyList
private long maxSize;
private Node head;
private Node tail;
private long size;
private Map map;
public MyList(long maxSize, Map map)
this.maxSize = maxSize;
this.map = map;
/**
* 直接添加到tail
*
* @param node
*/
public void add(Node node)
size++;
if (null == head)
//head为空,说明此时队列中一个数据也没有
head = node;
tail = node;
else
node.setPre(tail);
tail.setNext(node);
tail = node;
//超过容量后,将head元素删除
if (size > maxSize)
map.remove(head.getKey());
Node next = head.getNext();
next.setPre(null);
head.setNext(null);
head = next;
size--;
public void remove(Node node)
if (null == head) //队列为空
return;
else if (head == node && tail == node) //只有一个元素时
head = null;
tail = null;
size--;
else
Node pre = node.getPre();
Node next = node.getNext();
if (null != pre)
pre.setNext(next);
else
head = next;
if (null != next)
next.setPre(pre);
else
tail = pre;
node.setPre(null);
node.setNext(null);
size--;
public void get(Node node)
remove(node);
add(node);
public void update(Node node)
remove(node);
add(node);
@Override
public String toString()
String result = "";
if (head == null)
return result;
else
Node next = head;
result += next + ", ";
while ((next = next.getNext()) != null)
result += next + ", ";
return result;
最后封装LRU
public class LRU implements ILru
private HashMap<Object, Node> map = new HashMap();
private MyList list;
public LRU(long size)
list = new MyList(size, map);
@Override
public void put(Object key, Object value)
Node node = map.get(key);
if (node == null)
node = new Node(key, value);
map.put(key, node);
list.add(node);
else
node.setValue(value);
map.put(key, node);
list.update(node);
@Override
public void remove(String key)
list.remove(map.get(key));
map.remove(key);
@Override
public Object get(String key)
Node node = map.get(key);
if (node == null)
return null;
list.get(node);
return node.getValue();
@Override
public String toString()
return "LRU" +
"list=" + list +
'';
测试
测试代码
public class Main
public static void main(String[] args)
lruTest(new LRU(5));
System.out.println("-------------------------");
lruTest(new JavaLRUCache(5));
static void lruTest(ILru lru)
lru.put("a", 1);
System.out.println(lru.toString());
lru.put("b", 2);
System.out.println(lru.toString());
lru.put("c", 3);
System.out.println(lru.toString());
lru.put("d", 4);
System.out.println(lru.toString());
lru.get("a");
System.out.println("get a 1 " + lru.toString());
lru.get("b");
System.out.println("get b 2 " + lru.toString());
lru.get("b");
System.out.println("get b 2 " + lru.toString());
lru.get("d");
System.out.println("get d 4 " + lru.toString());
lru.put("e", 5);
System.out.println(lru.toString());
lru.put("f", 6);
System.out.println(lru.toString());
lru.put("g", 7);
System.out.println(lru.toString());
lru.remove("g");
System.out.println("remove g 7 " + lru.toString());
lru.remove("e");
System.out.println("remove e 5 " + lru.toString());
lru.put("e", 5);
System.out.println(lru.toString());
lru.put("e", 9);
System.out.println(lru.toString());
lru.put("f", "f");
System.out.println(lru.toString());
输出:
LRUlist=a=1,
LRUlist=a=1, b=2,
LRUlist=a=1, b=2, c=3,
LRUlist=a=1, b=2, c=3, d=4,
get a 1 LRUlist=b=2, c=3, d=4, a=1,
get b 2 LRUlist=c=3, d=4, a=1, b=2,
get b 2 LRUlist=c=3, d=4, a=1, b=2,
get d 4 LRUlist=c=3, a=1, b=2, d=4,
LRUlist=c=3, a=1, b=2, d=4, e=5,
LRUlist=a=1, b=2, d=4, e=5, f=6,
LRUlist=b=2, d=4, e=5, f=6, g=7,
remove g 7 LRUlist=b=2, d=4, e=5, f=6,
remove e 5 LRUlist=b=2, d=4, f=6,
LRUlist=b=2, d=4, f=6, e=5,
LRU以上是关于使用LinkedHashMap实现LRU算法的主要内容,如果未能解决你的问题,请参考以下文章