概述

从名字上看LinkedHashMap相比于HashMap,显然多了链表的实现。从功能上看,LinkedHashMap有序,HashMap无序。这里的顺序指的是添加顺序或者访问顺序。

基本使用

  1. @Test
  2. public void test1(){
  3. LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
  4. map.put(1,1);
  5. map.put(3,3);
  6. map.put(4,4);
  7. map.put(2,2);
  8. for (Map.Entry entry : map.entrySet()) {
  9. System.out.println(entry.getKey() + ":" + entry.getValue());
  10. }
  11. map.get(1);
  12. map.get(2);
  13. System.out.println();
  14. for (Map.Entry entry : map.entrySet()) {
  15. System.out.println(entry.getKey() + ":" + entry.getValue());
  16. }
  17. }

我们通过构造函数的第三个参数accessOrder设为true(默认为false),该参数为true,则根据访问顺序排序。如下,当我们调用get()方法的时候,再次遍历发现数据被放到了最后面。

  1. 1:1
  2. 3:3
  3. 4:4
  4. 2:2
  5. 3:3
  6. 4:4
  7. 1:1
  8. 2:2

源码分析

首先LinkedHashMap继承了HashMap,因此其基本使用与HashMap几乎完全一致。
可以看到内部实现了双向链表 head指向最久访问,tail指向最新访问。

  1. /**
  2. * The head (eldest) of the doubly linked list.
  3. */
  4. transient LinkedHashMap.Entry<K,V> head;
  5. /**
  6. * The tail (youngest) of the doubly linked list.
  7. */
  8. transient LinkedHashMap.Entry<K,V> tail;
  9. /**
  10. * HashMap.Node subclass for normal LinkedHashMap entries.
  11. */
  12. static class Entry<K,V> extends HashMap.Node<K,V> {
  13. Entry<K,V> before, after; // Entry类维护了双线链表所需的前后节点指向
  14. Entry(int hash, K key, V value, Node<K,V> next) {
  15. super(hash, key, value, next);
  16. }
  17. }

既然有了双向链表的基本定义,那么他是怎么实现双向链表的呢?找了这个类,发现并没有重写put方法,说明直接使用父类的put。那么直接看HashMap的putVal()方法。HashMap的源码就不再详细讲了,感兴趣的朋友可以看看我的另一篇文章。
观察HashMap的putVal的时候我们发现创建节点会频繁的使用到newNode()方法。

  1. if ((tab = table) == null || (n = tab.length) == 0)
  2. n = (tab = resize()).length;
  3. if ((p = tab[i = (n - 1) & hash]) == null)
  4. tab[i] = newNode(hash, key, value, null); //添加节点的时候使用newNode
  5. else {
  6. ......省略

我们观察LinkedHashMap,果然,在这里重写了,将每个节点添加到双向链表中。

  1. Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
  2. LinkedHashMap.Entry<K,V> p =
  3. new LinkedHashMap.Entry<K,V>(hash, key, value, e);
  4. linkNodeLast(p);
  5. return p;
  6. }
  7. private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
  8. LinkedHashMap.Entry<K,V> last = tail;
  9. tail = p;
  10. if (last == null)
  11. head = p;
  12. else {
  13. p.before = last;
  14. last.after = p;
  15. }
  16. }

前面已经看到了put操作,LinkedHashMap在put方法执行的时候维护了一个双向链表。接下来我们看一下get操作。

  1. public V get(Object key) {
  2. Node<K,V> e;
  3. if ((e = getNode(hash(key), key)) == null) // 调用父类的getNode
  4. return null;
  5. if (accessOrder) // accessOrder为true则调整双向链表顺序
  6. afterNodeAccess(e);
  7. return e.value;
  8. }
  9. void afterNodeAccess(Node<K,V> e) { // move node to last
  10. LinkedHashMap.Entry<K,V> last;
  11. if (accessOrder && (last = tail) != e) { // accessOrder为true且 尾节点不为当前元素
  12. LinkedHashMap.Entry<K,V> p =
  13. (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  14. p.after = null;
  15. if (b == null)
  16. head = a;
  17. else
  18. b.after = a;
  19. if (a != null)
  20. a.before = b;
  21. else
  22. last = b;
  23. if (last == null)
  24. head = p;
  25. else {
  26. p.before = last;
  27. last.after = p;
  28. }
  29. tail = p;
  30. ++modCount;
  31. }
  32. }

当我们将accessOrder设为true的时候,LinkedHashMap会将当前元素调整到双向链表末尾。

LRU缓存的实现

LRU(Least Recently Used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

根据定义,为了实现LRU,LinkedHashMap我们需要将accessOrder设为true。以保证最新使用的可以调整到链表的末尾。那么另一个比较重要的就是当缓存满的时候,需要淘汰最久没有使用的数据,及链表的头。

因此,该操作应该是存在put方法执行的过程中,由于LinkedHashMap并没有实现put方法,那么继续看HashMap里面的putVal(); 在方法快结束的时候发现了一个有趣的类,从方法名就感觉可以从这里入手。

  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true); //最后一个参数即下面的evict为true
  3. }
  4. afterNodeInsertion(evict); // HashMap并没有对这个类进行实现。

查看LinkedHashMap的afterNodeInsertion

  1. void afterNodeInsertion(boolean evict) { // possibly remove eldest
  2. LinkedHashMap.Entry<K,V> first;
  3. if (evict && (first = head) != null && removeEldestEntry(first)) { // 我们只需要在这里重写移除最久没有使用的节点的判断条件,就可以移除节点了
  4. K key = first.key;
  5. removeNode(hash(key), key, null, false, true);
  6. }
  7. }

我们假设LRU里最大容量为16,只需要将构造方法里的accessOrder设为true,并且重写removeEldestEntry的判断条件就可以了。

  1. public class LruLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
  2. private Integer capacity;
  3. public LruLinkedHashMap(int capacity) {
  4. super(capacity, 0.75f, true);
  5. this.capacity = capacity;
  6. }
  7. @Override
  8. protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
  9. return size() > capacity;
  10. }
  11. public static void main(String[] args) {
  12. LruLinkedHashMap<Integer, Integer> lru = new LruLinkedHashMap(16);
  13. for (int i = 0; i < 16; i++) {
  14. lru.put(i, i);
  15. }
  16. System.out.println("当前lru缓存的元素为");
  17. for (Map.Entry entry : lru.entrySet()) {
  18. System.out.println(entry.getKey() + ":" + entry.getValue());
  19. }
  20. lru.get(0);
  21. lru.get(1);
  22. System.out.println("当前lru缓存的元素为");
  23. for (Map.Entry entry : lru.entrySet()) {
  24. System.out.println(entry.getKey() + ":" + entry.getValue());
  25. }
  26. lru.put(16, 16);
  27. lru.put(17, 17);
  28. System.out.println("当前lru缓存的元素为");
  29. for (Map.Entry entry : lru.entrySet()) {
  30. System.out.println(entry.getKey() + ":" + entry.getValue());
  31. }
  32. }
  33. }

测试结果如下,最左边的为最近最久未使用。

  1. 当前lru缓存的元素为
  2. 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15
  3. 当前lru缓存的元素为
  4. 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15 0:0 1:1
  5. 当前lru缓存的元素为
  6. 4:4 5:5 6:6 7:7 8:8 9:9 10:10 11:11 12:12 13:13 14:14 15:15 0:0 1:1 16:16 17:17

LinkedHashMap源码分析及实现LRU的更多相关文章

  1. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  2. LinkedHashMap源码分析

    hashMap源码分析:hashMap源码分析 版本说明:jdk1.7LinkedHashMap继承于HashMap,是一个有序的Map接口的实现.有序指的是元素可以按照一定的顺序排列,比如元素的插入 ...

  3. Java 容器 LinkedHashMap源码分析1

    同 HashMap 一样,LinkedHashMap 也是对 Map 接口的一种基于链表和哈希表的实现.实际上, LinkedHashMap 是 HashMap 的子类,其扩展了 HashMap 增加 ...

  4. 死磕 java集合之LinkedHashMap源码分析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 LinkedHashMap内部维护了一个双向链表,能保证元素按插入的顺序访问,也能以访问 ...

  5. java Linkedhashmap源码分析

    LinkedHashMap类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是插入次序,或者是最近最少使用(LRU)的次序.只比HashMap慢一点:而在迭代访问时反而更快,因为它使用链表维 ...

  6. Java8集合框架——LinkedHashMap源码分析

    本文的结构如下: 一.LinkedHashMap 的 Javadoc 文档注释和简要说明 二.LinkedHashMap 的内部实现:一些扩展属性和构造函数 三.LinkedHashMap 的 put ...

  7. Java集合之LinkedHashMap源码分析

    概述 HashMap是无序的, 即put的顺序与遍历顺序不保证一样. LinkedHashMap是HashMap的一个子类, 它通过重写父类的相关方法, 实现自己的功能. 它保留插入的顺序. 如果需要 ...

  8. LinkedHashMap 源码分析

    LinkedHashMap LinkedHashMap 能解决什么问题?什么时候使用 LinkedHashMap? 1)LinkedHashMap 按照键值对的插入顺序进行遍历,LinkedHashM ...

  9. HashMap LinkedHashMap源码分析笔记

    MapClassDiagram

随机推荐

  1. 浏览器仿EXCEL表格插件 版本更新 - 智表ZCELL产品V1.3.2更新

    智表(zcell)是一款浏览器仿excel表格jquery插件.智表可以为你提供excel般的智能体验,支持双击编辑.设置公式.设置显示小数精度.下拉框.自定义单元格.复制粘贴.不连续选定.合并单元格 ...

  2. 单向链表的Java实现

    package testOffer.linkedList; import org.w3c.dom.Node; public class SingleLinkedList { //测试用例 public ...

  3. 算法学习之BFS、DFS入门

    算法学习之BFS.DFS入门 0x1 问题描述 迷宫的最短路径 给定一个大小为N*M的迷宫.迷宫由通道和墙壁组成,每一步可以向相邻的上下左右四格的通道移动.请求出从起点到终点所需的最小步数.如果不能到 ...

  4. Django Rest Framework(二)

    •基于Django 先创建一个django项目,在项目中创建一些表,用来测试rest framework的各种组件 models.py class UserInfo(models.Model): &q ...

  5. js数组中随机选取一个数值!!

    var arr = ["太阳光大","成功是优点的发挥","不要小看自己", "口说好话","手心向下是助人& ...

  6. iOS CATransition 自定义转场动画

    https://www.jianshu.com/p/39c051cfe7dd CATransition CATransition 是CAAnimation的子类(如下图所示),用于控制器和控制器之间的 ...

  7. 播放器授权后播放内容时出现Cnario logo水印

    问题描述 Player获取License后, 通过Messeenger发布到Player的内容前面出现Cnario 的logo水印, 如下图样式: 原因 出现这种情况一般是由于License授权不正确 ...

  8. MySQL之日期时间类型

    mysql(5.5)所支持的日期时间类型有:DATETIME. TIMESTAMP.DATE.TIME.YEAR. 几种类型比较如下: 日期时间类型 占用空间 日期格式 最小值 最大值 零值表示  D ...

  9. 解决win环境下访问本机虚拟机中centos7 ftp服务器的问题

    inux搭建ftp服务器 1.安装软件: yum install vsftpd 2.修改配置文件vsftpd.conf: vim /etc/vsftpd/vsftpd.conf 把anonymous_ ...

  10. 【tool】VLC播放rtmp协议

    您的输入无法被打开: VLC 无法打开 MRL「rtmp://112.35.3.71:47201/api/6276-0.1546939632724.flv」.详情请检查日志.