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;

}

调用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算法相关推荐

  1. java mysql lru_Java集合详解5:深入理解LinkedHashMap和LRU缓存

    今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存. 摘要:HashMap和双向链表合二为一即是LinkedHashMap.所谓Linked ...

  2. Java集合详解5:深入理解LinkedHashMap和LRU缓存

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  3. LinkedHashMap实现LRU算法

    LinkedHashMap 概述 笔者曾提到,HashMap 是 Java Collection Framework 的重要成员,也是Map族(如下图所示)中我们最为常用的一种.不过遗憾的是,Hash ...

  4. 缓存基础----LRU算法和FIFO算法的Java实现

    Java里面实现LRU缓存算法的通常有两种选择,一种是自己设计数据结构:链表+HashMap(链表用来表示位置,哈希表用来存储和查找),另一种是使用Java中的LinkedHashMap.我们这边文章 ...

  5. 通过分析LinkedHashMap了解LRU

    我们都知道LRU是最近最少使用,根据数据的历史访问记录来进行淘汰数据的.其核心思想是如果数据最近被访问过,那么将来访问的几率也更高.在这里提一下,Redis缓存和MyBatis二级缓存更新策略算法中就 ...

  6. java中各种集合的用法和比较

    一,java中各种集合的关系图 Collection       接口的接口     对象的集合  ├ List           子接口         按进入先后有序保存   可重复  │├ L ...

  7. Java中Set集合是如何实现添加元素保证不重复的?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | 公众号「武培轩」 Java中Set集合是如何实 ...

  8. 【Java 集合】Java 集合的线程安全性 ( 加锁同步 | java.utils 集合 | 集合属性 | java.util.concurrent 集合 | CopyOnWrite 机制 )

    文章目录 I . Java 集合的线程安全概念 ( 加锁同步 ) II . 线程不安全集合 ( 没有并发需求 推荐使用 ) III . 集合属性说明 IV . 早期的线程安全集合 ( 不推荐使用 ) ...

  9. Thinking in java基础之集合框架

    Thinking in java基础之集合框架 大家都知道我的习惯,先上图说话. 集合简介(容器) 把具有相同性质的一类东西,汇聚成一个整体,就可以称为集合,例如这里有20个苹果,我们把每一个苹果当成 ...

最新文章

  1. 值得期待的.Net Micro Framework 3.0
  2. java pom.xml 自定义变量
  3. argparse模块用法
  4. shell基础之变量及表达式
  5. 怎么扒站建站_个人怎么做独立站Shopify商店!Shopify建站教程详解!(实操干货)...
  6. js 图片浏览插件原生
  7. Advanced.MP3.WMA.Recorder.v5.8.WinAll-CAT
  8. CAD导出JPG如何控制转换质量?
  9. jeecgboot修改登录界面、背景图等的页面记录
  10. 对称网络的电路分析方法
  11. 安卓socket客户端
  12. iOS微信发布8.0.29版本,苹果14用户快来
  13. (全)Word Embedding
  14. Linux命令行修改配置(待续)
  15. 剑指offer答案 c语言,剑指offer之C语言不修改数组找出重复的数字
  16. c语言编程解释,c语言编程,请高手一字一句解释
  17. ps cc2019版为什么做图一复制图层就卡死_PS制作一张具有故障艺术效果的人物海报...
  18. Access to XMLHttpRequest at ‘http://xx‘ from origin ‘http://xx‘ has been blocked by CORS policy:
  19. 公司企业邮箱,邮件归档和邮件备份有什么用途?
  20. 图片懒加载——lazyload

热门文章

  1. 乱码的原因和发展(关于编码)
  2. 松下AJ-HPX3100摄像机误删除MXF视频文件数据恢复
  3. 什么是线程?什么是进程?
  4. Java三大框架,小白必知!
  5. Spring 生命周期详解
  6. for loop循环
  7. 小米4c无信号恢复服务器,小米4C用户必知,这8个问题及解决方法一定要了解
  8. 信奥赛一本通1099第n小的质数
  9. WMI控件属性异常修复
  10. java 内省机制_Java反射及 IoC原理、内省机制