我是靠谱客的博主 孝顺路人,最近开发中收集的这篇文章主要介绍java lru lfu_Java集合之LinkedHashMap实现LRU,LFU,FIFO算法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

LinkedHashMap=双向链表+HashMap,存储相比HashMap会多了一个前节点,后节点.

LinkedHashMap简介

LinkedHashMap主要是通过HashMap+双向链表来实现的,主要作用目前就个人使用来看,可以主要有插入排序,访问排序.有了这两个排序就可以很简单的实现LRU,FIFO缓存算法.至于怎么用,就看个人的改造能力了.下面将会分析源码,以及用它来实现各种缓存算法.

LinkedHashMap源码分析

在jdk1.8的HashMap中多了三个方法里面什么都没做,为的就是留给LinkedHashMap去实现

void afterNodeAccess(Nodep) { }节点访问后:重复插入相同的key值或者get过该key,都会将该元素移动至队列尾部

void afterNodeInsertion(boolean evict) { }节点插入后:节点插入后做什么操作呢?队列满的时候删除队列头部的元素

void afterNodeRemoval(Nodep) { }节点移除后:移除节点后操作

这三个方法实在HashMap中定义的,但是具体的实现是在LinkedHashMap中实现的,这也是LinkedHashMap的意义所在.

源码分析

public class LinkedHashMap

extends HashMap

implements Map{

//在HashMap的节点上包装了一个前节点,尾节点

static class Entry extends HashMap.Node {

Entry before, after;

Entry(int hash, K key, V value, Node next) {

super(hash, key, value, next);

}

}

//Linked是一个双向链表的,这个是链表头节点,下面是尾节点

transient LinkedHashMap.Entry head;

transient LinkedHashMap.Entry tail;

// true:按访问顺序存储(LRU),false:按存入顺序存储(FIFO,先进先出)

final boolean accessOrder;

}

7dd5815b1b74a616fffb2c6ca373ef89.png

调用put方法时,其实调用的是HashMap的方法,HashMap在newNode时,LinkeHashMap重写了该方法.

Node newNode(int hash, K key, V value, Node e) {

LinkedHashMap.Entry p =

new LinkedHashMap.Entry(hash, key, value, e);

linkNodeLast(p); //将该节点拼接为一个linkNode

return p;//返回node节点

}

//这里就是组装一个双向链表哦.

private void linkNodeLast(LinkedHashMap.Entry p) {

LinkedHashMap.Entry last = tail;

tail = p;

if (last == null)

head = p;

else {

p.before = last;

last.after = p;

}

}

从上面可以看出同一个node,被用来存储到HashMap中,又被用来组成一个链表.相当于双重数据结构.并不意味着数据就是两份,要知道node只有一个哦,Java里面通过引用多个值引用同一个对象.就如:

Object o=new Object();

Object a=o;

Object b=o;

这里始终只有一个对象.他们三个都引用了o这个对象.

afterNodeInsertion

在HashMap的put方法最后有这么一句:afterNodeInsertion(evict);LinkedHashMap重写了之后

void afterNodeInsertion(boolean evict) { // 是否移除头部元素

LinkedHashMap.Entry first;

if (evict && (first = head) != null && removeEldestEntry(first)) {

K key = first.key;

removeNode(hash(key), key, null, false, true);

}

}

//是否删除元素

protected boolean removeEldestEntry(Map.Entry eldest) {

return false;

}

afterNodeAccess

在put方法中还调用了afterNodeAccess(e);都知道HashMap可以重复添加元素(重复key相当于更新value,key始终是唯一的),在HashMap中已经有key1了,这个时候在put(key1,..)那就是更新value值,不管是get,还是put更新都会触发afterNodeAccess(访问后)方法,不管是更新还是访问都是一种访问.

HashMap--put方法中出发:

void afterNodeAccess(Node e) { // 将访问的元素e移动到队列尾部

LinkedHashMap.Entry last;

if (accessOrder && (last = tail) != e) {

LinkedHashMap.Entry p =

(LinkedHashMap.Entry)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;

}

}

LinkedHashMap中重写了HashMap的get方法,简单明了

public V get(Object key) {

Node e;

if ((e = getNode(hash(key), key)) == null)

return null;

if (accessOrder) //是否按访问排序,如果等于true则调整顺序

afterNodeAccess(e);

return e.value;

}

这段代码自己画个图更一下就会明白.

node1 node2 node3 node4

访问了node2或者更新了node2,都会出发afterNodeAccess方法,该方法就是将node2移动值链表尾部.移动后变成

node1 node3 node4 node2

奇怪点解说

void afterNodeAccess(Node e){

LinkedHashMap.Entry p = (LinkedHashMap.Entry)e

}

在put是,newNode的时候LinkedHashMap创建的是一个LinkedHashMap.Entry但是返回的是一个Node数据给HashMap,HashMap拿到的这个Node其实里面隐藏了Entry的前节点后节点.只不过通过Node无法直接访问.

当节点传递过来之后(LinkedHashMap.Entry)e 强转义之后,before,after节点就会自动回来的.

afterNodeRemoval

LinkedHashMap继承了HashMap, LinkedHashMap只在有需要的地方重写了HashMap的方法,其他都是原用HashMap的方法的,同时它并没有改变HashMap的任何结构.只是自己扩展了一个链表.在LinkedHashMap中删除操作就没有任何重写操作,所以HashMap中删除一个元素时,同时也需要在LinkedHashMap中的链表中删除该节点.这个时候就有了扩展方法afterNodeRemoval,只是为了HashMap删除的同时删除在链表中也将该元素删除.

void afterNodeRemoval(Node e) { // unlink

LinkedHashMap.Entry p =

(LinkedHashMap.Entry)e, b = p.before, a = p.after;

p.before = p.after = null;

if (b == null)

head = a;

else

b.after = a;

if (a == null)

tail = b;

else

a.before = b;

}

FIFO算法

FIFO(先进先出)的思想是基于队列。如果一个数据是最先进入的,那么可以认为在将来它被访问的可能性很小。空间满的时候,最先进入的数据会被最早置换(淘汰)掉。

适合热点数据为新数据,最先存入的数据为最老的数据,最后存入的为新数据,当超过队列长度时,删除队列头部数据(老数据)即可.

private static class MyFIFO extends LinkedHashMap {

protected int maxElements;

public MyFIFO(Integer maxSize) {

super(maxSize, 0.75f, false); //FIFO false:访问元素时,不移动。

this.maxElements = maxSize;

}

//当总数大于最大值时,执行删除队列头的动作

protected boolean removeEldestEntry(Map.Entry eldest) {

return this.size() > this.maxElements;

}

}

private static void test(Map map) {

//true表示如果最近访问的,将数据移动到队列尾部。

for (int i = 0; i < 20; i++) {

if (i == 11) {

map.get("k8");//访问后,元素被移动到队列尾部去了。

}

if (i == 18) {

map.get("k14");

}

map.put("k" + i, i);

}

System.out.println("===开始遍历元素====");

for (Map.Entry m : map.entrySet()) {

System.out.println(m.getKey() + "," + m.getValue());

}

}

LRU算法

LRU(The Least Recently Used,最近最久未使用算法)的思想是基于时间,如果一个元素最久没有使用到,那么在将来中使用到的几率也最少,可以将最久未使用的元素从容器中剔除。

可以利用LinkedHashMap的访问顺序存储.被访问过元素移动到队列尾部,长时间未被访问的元素自然在队列头部了,当存入的元素大于队列时,只需要移除队列头的元素即可.

private static class MyLRU extends LinkedHashMap {

protected int maxElements;

public MyLRU(Integer maxSize) {

super(maxSize, 0.75f, true); //区分点true:每次访问元素,都将元素移动到队列尾部

this.maxElements = maxSize;

}

//当总数大于最大值时,执行删除队列头的动作

protected boolean removeEldestEntry(Map.Entry eldest) {

return this.size() > this.maxElements;

}

}

LFU算法

这个如果用redis来实现就会更简单了。

LFU(Least Frequently Used ,最近最少未使用算法)的思想是基于频率,如果一个元素的使用频率最少,那么在将来使用到的情况也最少.这个算法需要记录元素的访问次数,然后淘汰访问次数最少的元素.

LFU和LRU看似相同,但其实不同,不同点在于新加入的元素。

LRU是每次访问都把元素插入到队列尾,新加入的元素也在队列尾巴,超出队列规定长度后淘汰队列头部。

缺点比如:1,2,3,4,5,6,7,8,9,10都是经常被访问的数据,但是队列大小只有10,这个时候插入11也许并不是热点数据,但是规则的问题还是会把1给删掉.

LFU是把每次访问的元素值+1,淘汰count值最小的。根据访问量排序,把访问量少的给淘汰掉。

LFU也有缺点,那就是新加入的元素由于没有访问量,也许就被误杀了.

private static class MyLFU extends HashMap {

public PriorityQueue> queue = new PriorityQueue<>();

protected int maxElements;

public MyLFU(Integer maxSize) {

this.maxElements = maxSize;

}

public V put(K k, V v) {

queue.add(new Node<>(k, 1));//初始值为1

if (size() > maxElements) {

//poll弹出队列头

remove(queue.poll().key);

}

return super.put(k, v);

}

public V get(Object k) {

for (Node kNode : queue) {

if (k != null && k.equals(kNode.key)) {

kNode.setCount(kNode.getCount() + 1);

}

}

return super.get(k);

}

}

//排序后,count值小的排在最前面,当执行poll时,count值最小的也就会被最先弹出。

private static class Node implements Comparable> {

private K key;

private int count;

public Node(K key, int count) {

this.key = key;

this.count = count;

}

public K getKey() {

return key;

}

public int getCount() {

return count;

}

public void setCount(int count) {

this.count = count;

}

@Override

public String toString() {

return "[key=" + key + ", count=" + count + "]";

}

@Override

public int compareTo(Node o) {

return this.count - o.count;

}

}

private static void testLFU() {

MyLFU map = new MyLFU<>(10);

for (int i = 0; i < 20; i++) {

if (i == 9) {

map.get("k8");//访问后,元素被移动到队列尾部去了。

}

if (i == 6) {

map.get("k5");

map.get("k3");

}

map.put("k" + i, i);

}

for (int i = 0, n = map.queue.size(); i < n; i++) {

System.out.println(map.queue.poll());

}

}

最后

以上就是孝顺路人为你收集整理的java lru lfu_Java集合之LinkedHashMap实现LRU,LFU,FIFO算法的全部内容,希望文章能够帮你解决java lru lfu_Java集合之LinkedHashMap实现LRU,LFU,FIFO算法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(52)

评论列表共有 0 条评论

立即
投稿
返回
顶部