LinkedHashMap 概述

笔者曾提到,HashMap 是 Java Collection Framework 的重要成员,也是Map族(如下图所示)中我们最为常用的一种。不过遗憾的是,HashMap是无序的,也就是说,迭代HashMap所得到的元素顺序并不是它们最初放置到HashMap的顺序。

HashMap的这一缺点往往会造成诸多不便,因为在有些场景中,我们确需要用到一个可以保持插入顺序的Map。庆幸的是,JDK为我们解决了这个问题,它为HashMap提供了一个子类 —— LinkedHashMap。虽然LinkedHashMap增加了时间和空间上的开销,但是它通过维护一个额外的双向链表保证了迭代顺序。

特别地,该迭代顺序可以是插入顺序,也可以是访问顺序。因此,根据链表中元素的顺序可以将LinkedHashMap分为:保持插入顺序的LinkedHashMap和保持访问顺序的LinkedHashMap,其中LinkedHashMap的默认实现是按插入顺序排序的。

在LinkedHashMapMap中,所有put进来的Entry都保存在如下面第一个图所示的哈希表中,但由于它又额外定义了一个以head为头结点的双向链表(如下面第二个图所示),因此对于每次put进来Entry,除了将其保存到哈希表中对应的位置上之外,还会将其插入到双向链表的尾部。

更直观地,下图很好地还原了LinkedHashMap的原貌:HashMap和双向链表的密切配合和分工合作造就了LinkedHashMap。特别需要注意的是,next用于维护HashMap各个桶中的Entry链,before、after用于维护LinkedHashMap的双向链表,虽然它们的作用对象都是Entry,但是各自分离,是两码事儿。

其中,HashMap与LinkedHashMap的Entry结构示意图如下图所示:

LinkedHashMap 存取小结

LinkedHashMap的存取过程基本与HashMap基本类似,只是在细节实现上稍有不同,这是由LinkedHashMap本身的特性所决定的,因为它要额外维护一个双向链表用于保持迭代顺序。

在put操作上,虽然LinkedHashMap完全继承了HashMap的put操作,但是在细节上还是做了一定的调整,比如,在LinkedHashMap中向哈希表中插入新Entry的同时,还会通过Entry的addBefore方法将其链入到双向链表中。

在扩容操作上,虽然LinkedHashMap完全继承了HashMap的resize操作,但是鉴于性能和LinkedHashMap自身特点的考量,LinkedHashMap对其中的重哈希过程(transfer方法)进行了重写。在读取操作上,LinkedHashMap中重写了HashMap中的get方法,通过HashMap中的getEntry方法获取Entry对象。在此基础上,进一步获取指定键对应的值。

LinkedHashMap 与 LRU(Least recently used,最近最少使用)算法

到此为止,我们已经分析完了LinkedHashMap的存取实现,这与HashMap大体相同。LinkedHashMap区别于HashMap最大的一个不同点是,前者是有序的,而后者是无序的。为此,LinkedHashMap增加了两个属性用于保证顺序,分别是双向链表头结点header和标志位accessOrder。

我们知道,header是LinkedHashMap所维护的双向链表的头结点,而accessOrder用于决定具体的迭代顺序。实际上,accessOrder标志位的作用可不像我们描述的这样简单,我们接下来仔细分析一波~

我们知道,当accessOrder标志位为true时,表示双向链表中的元素按照访问的先后顺序排列,可以看到,虽然Entry插入链表的顺序依然是按照其put到LinkedHashMap中的顺序,但put和get方法均有调用recordAccess方法(put方法在key相同时会调用)。

recordAccess方法判断accessOrder是否为true,如果是,则将当前访问的Entry(put进来的Entry或get出来的Entry)移到双向链表的尾部(key不相同时,put新Entry时,会调用addEntry,它会调用createEntry,该方法同样将新插入的元素放入到双向链表的尾部,既符合插入的先后顺序,又符合访问的先后顺序,因为这时该Entry也被访问了);

当标志位accessOrder的值为false时,表示双向链表中的元素按照Entry插入LinkedHashMap到中的先后顺序排序,即每次put到LinkedHashMap中的Entry都放在双向链表的尾部,这样遍历双向链表时,Entry的输出顺序便和插入的顺序一致,这也是默认的双向链表的存储顺序。

因此,当标志位accessOrder的值为false时,虽然也会调用recordAccess方法,但不做任何操作。

get操作与标志位accessOrder

public V get(Object key) {// 根据key获取对应的Entry,若没有这样的Entry,则返回nullEntry<K,V> e = (Entry<K,V>)getEntry(key); if (e == null)      // 若不存在这样的Entry,直接返回return null;e.recordAccess(this);return e.value;
}

使用LinkedHashMap实现LRU算法

如下所示,笔者使用LinkedHashMap实现一个符合LRU算法的数据结构,该结构最多可以缓存6个元素,但元素多余六个时,会自动删除最近最久没有被使用的元素,如下所示:

public class LRU<K,V> extends LinkedHashMap<K, V> implements Map<K, V>{private static final long serialVersionUID = 1L;public LRU(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor, accessOrder);}/** * @description 重写LinkedHashMap中的removeEldestEntry方法,当LRU中元素多余6个时,*              删除最不经常使用的元素* @author rico       * @created 2017年5月12日 上午11:32:51      * @param eldest* @return     * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)     */  @Overrideprotected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {// TODO Auto-generated method stubif(size() > 6){return true;}return false;}public static void main(String[] args) {LRU<Character, Integer> lru = new LRU<Character, Integer>(16, 0.75f, true);String s = "abcdefghijkl";for (int i = 0; i < s.length(); i++) {lru.put(s.charAt(i), i);}System.out.println("LRU中key为h的Entry的值为: " + lru.get('h'));System.out.println("LRU的大小 :" + lru.size());System.out.println("LRU :" + lru);}
}

LinkedHashMap实现LRU算法相关推荐

  1. 使用LinkedHashMap实现LRU算法

    LRU算法,最近最少使用原则,如果要实现该算法,可以借助LinkedHashMap数据结构,LinkedHashMap继承HashMap,底层使用哈希表和双向链表来保存所有元素,使用LinkedHas ...

  2. 利用LinkedHashMap实现LRU算法

    import java.util.LinkedHashMap; import java.util.Map; import java.util.Set;public class LruCache< ...

  3. linkedhashmap遍历_Java集合:浅谈LinkedHashMap、LinkedHashSet源码及LRU算法实现

    Java的HashSet.HashMap集合应用及底层原理,相信大家都已经很熟悉了,这里就不再赘述了.这里主要来介绍下如何Java中的LinkedHashMap集合,同时也介绍下基于LinkedHas ...

  4. java 最少使用(lru)置换算法_缓存置换算法 - LRU算法

    LRU算法 1 原理 对于在内存中并且不被使用的数据块就是LRU,这类数据需要从内存中删除,以腾出空间来存储常用的数据. LRU算法(Least Recently Used,最近最少使用),是内存管理 ...

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

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

  6. 01 guava-cache:LRU算法

    文章目录 LRU算法介绍 LRU算法实现 使用LinkedHashMap实现 LRU算法介绍 最近最久未使用(LRU)算法,其核心思想是"如果数据最近被访问过,那么将来被访问的几率也更高&q ...

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

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

  8. java 最少使用(lru)置换算法_「面试」LRU了解么?看看LinkedHashMap如何实现LRU算法...

    以下内容均是本人原创,希望你看完之后能有更多更深入的了解,欢迎关注➕ 问题:使用Java完成一个简单的LRU算法 什么是LRU算法 LRU(Least Recently Used),也就是最近最少使用 ...

  9. LinkedHashMap实现LRU缓存算法

    缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的. LRU这个算法就是把最近一次使用时间离现在时间最远的数据删除掉. 先说说List:每次访问一个 ...

最新文章

  1. [bzoj4562][Haoi2016]食物链_记忆化搜索_动态规划
  2. 缇 、 像素 、 厘米
  3. Windows下的socket演示程序
  4. python语句x 3 3执行_Python 3.x 学习笔记--杂
  5. 复杂对象的组装与创建——建造者模式
  6. 2012年12月第二个周末
  7. python应用体系_python-大型django应用程序体系结构
  8. python类中的特殊方法_Python中类的初始化特殊方法
  9. 高德地图如何取消订单_一文教你如何爬取高德地图
  10. mysql的常见命令与语法规范
  11. Particle Designer:粒子效果制作器,生成plist文件并在工程中正常使用
  12. amd opencl使用低版本驱动
  13. 【报名开启】2021年博客之星总评选,属于你的年终表彰
  14. Error: ImageIO: PNG invalid PNG file: iDOT doesn't point to valid IDAT chunk 解决
  15. 手机app推广渠道的安装来源追踪与归因
  16. 多模光纤与单模光纤熔接及用哪种光模块的问题。
  17. python自动化部署hadoop集群_大数据集群的自动化运维实现思路
  18. 公钥基础设施 PKI 技术与应用发展
  19. border-image
  20. Omit和Exclude的区别

热门文章

  1. 浴血凤凰2020年最新全自动辅助开发教程(二次更新)
  2. 个人bashrc配置
  3. 关于30岁前的几点感慨
  4. 20行Python代码!把B站直播间的小姐姐占为己有
  5. ssh-keygen生成公钥及可能遇到的问题
  6. 女性手机行业投资暗流涌动 朵唯或成黑马
  7. ‘latin-1‘ codec can‘t encode characters in position 0-10: ordinal not in ran
  8. 计算机基础及软件测试学习路线
  9. 微店调整交易手续费 借记卡和信用卡支付都将收取0.6%费用
  10. html表格边框为void,将表格边框设置为THICK(Setting Table borders to THICK)