如果你对 HashMap 的源码有了解的话,只需要一图就能知道 LinkedHashMap 的原理了,但是具体的实现细节还是需要去读一下源码。

一、LinkedHashMap 简介

1.1 继承结构

从继承结构上来讲 LinkedHashMap 继承自 HashMapLinkedHashMap 中没有提供任何增删改查的方法,而是直接复用了父类 HashMap 中的方法。

1.2 内部数据结构

LinkedHashMap 继承自 HashMapHashMap 底层存储键值对的数据结构是 NodeTreeNode,而 LinkedHashMap 存储键值对的数据结构是 EntryTreeNode

    // Entry 继承自 HashMap 中的 Node 类,用于链表存储键值对static class Entry<K,V> extends HashMap.Node<K,V> {// 前置与后置节点,用于维护 LinkedHashMap 的键值对顺序Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}}

Entry 用于存储链表中的键值对,用 beforenext 指针维护插入键值对的顺序,那树节点是怎么维护插入顺序的呢?在 LinkedHashMap 中是没有 TreeNode 类的,因为它复用了 HashMap 中的 TreeNode,下面我们来看一下 HashMapTreeNode 的定义。

我们只看这一行代码就行了,你会发现 HashMapTreeNode 类继承了 LinkedHashMapEntry,这样一来就继承了父类的前置与后置指针,也就能维护 LinkedHashMap 的插入顺序了。

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>

1.3 构造函数

我们只看其中一个构造函数

    public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {// 调用 HashMap 的构造函数super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}

LinkedHashMap 只有这一个构造函数可以初始化 accessOrder,这个 accessOrder 属性又有什么作用呢,下面来解释一下,看了解释大家可能还会有点迷惑,后面结合代码大家就能理解这个字段的作用了。

    /** 键值对迭代顺序策略* true:access-order     访问顺序,使用迭代器遍历时,get 的元素会被添加到最后* false:insertion-order 插入顺序*/final boolean accessOrder;

上面讲了那么多都是为相关源码分析作准备,下面我们就一起来看看吧。

二、添加键值对

上面我们也说了 LinkedHashMap 中没有 put 方法,因为都是复用的 HashMap 的,如果你想具体了解 put 方法,可以查看 HashMap 扩容机制与线程安全分析 这篇文章。

想要把键值对放到 LinkedHashMap 中去,一定会先封装成 EntryTreeNode 节点,知道这个原理我们直接来看 newNode(以链表节点为例) 方法。

    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {// 初始化 LinkedHashMap 键值对 EntryLinkedHashMap.Entry<K,V> p =new LinkedHashMap.Entry<K,V>(hash, key, value, e);// 添加的键值对都会被添加到 LinkedHashMap 尾部,因此可以形成一个双向链表linkNodeLast(p);return p;}

newNode 方法初始化 Entry 节点时调用了一个 linkNodeLast 方法,我想看到这里大家应该这道其中的原理了。

    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {// 获取并记录 tail 节点LinkedHashMap.Entry<K,V> last = tail;// 重置 tailtail = p;if (last == null)head = p;else {// 将节点连接起来,构成双向链表p.before = last;last.after = p;}}

三、获取键值对

LinkedHashMapget 方法调用了 HashMapgetNode 方法,获取到对应的节点后,返回对应的 value。

    public V get(Object key) {Node<K,V> e;// 调用 hashMap 中的 getNode() 方法,根据 key 的哈希值找到对应的桶位置,判断节点后(链表、头结点、树节点)进行返回if ((e = getNode(hash(key), key)) == null)return null;// 如果 accessOrder 为 true,获取元素后把当前键值对调整到尾部if (accessOrder)afterNodeAccess(e);return e.value;}

下面我们来看一下 getNode 方法都做了什么。这个方法做的事情很简答, 根据 key 计算出对应的哈希值,根据哈希值计算出对应的桶位置,然后遍历节点进行查找。

    final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// 进行判断并通过 tab[(n - 1) & hash] 计算当前 key 在哈希表中的位置if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 如果是当前桶位置上的头节点直接返回if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;// 如果头节点不是要找的节点,判断是树节点还是链表节点后继续查找if ((e = first.next) != null) {if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 链表、从头节点开始遍历查找do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;}

上面的 get 方法中还有一个需要注意的地方,如果 accessOrderatrue 则调用 fterNodeAccess(e); 方法,这个方法又是干嘛用的呢,在最开始的时候我们说过,LinkedHashMap 支持两种迭代策略,分别是访问顺序(默认)与插入顺序,这个 fterNodeAccess(e); 方法就是用于支持访问顺序的。

如果 accessOrderatrue,那么在调用 get 方法时会将该键值对置为双向链表的尾节点。

    void afterNodeAccess(Node<K,V> e) { // move node to lastLinkedHashMap.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;// 把当前节点的 after 节点置 nullp.after = null;// 如果当前节点是头节点,把后一个节点置为头节点if (b == null)head = a;// 把当前节点的前后节点相连elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;// 把当前节点置为尾节点并记录else {p.before = last;last.after = p;}tail = p;++modCount;}}

四、HashMap 与 LinkedHashMap 对比

除了双向链表外,HashMapLinkedHashMap 还有什么区别吗?

4.1 containsValue 方法对比

HashMap 中代码如下,遍历哈希表中的所有桶,然后遍历桶位置上的所有节点。

    public boolean containsValue(Object value) {Node<K,V>[] tab; V v;if ((tab = table) != null && size > 0) {// 遍历哈希表for (int i = 0; i < tab.length; ++i) {// 遍历当前桶上的所有节点for (Node<K,V> e = tab[i]; e != null; e = e.next) {if ((v = e.value) == value ||(value != null && value.equals(v)))return true;}}}return false;}

LinkedHashMap 中代码如下,从头节点遍历,一直遍历到尾节点。

    public boolean containsValue(Object value) {/*** 遍历双向链表,判断 value,与 HashMap 完全不同*/for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {V v = e.value;if (v == value || (value != null && value.equals(v)))return true;}return false;}

4.2 nextNode 方法对比

HashMap 中的 nextNode() 方法,遍历哈希表数组,接着遍历桶位置上的所有节点。

     final Node<K,V> nextNode() {// 记录哈希表数组Node<K,V>[] t;Node<K,V> e = next;if (modCount != expectedModCount)throw new ConcurrentModificationException();if (e == null)throw new NoSuchElementException();// 过滤掉没有键值对的桶位置if ((next = (current = e).next) == null && (t = table) != null) {do {} while (index < t.length && (next = t[index++]) == null);}// 下一个有键值对的桶(单个节点、树节点或链表)return e;}

LinkedHashMap 中的 nextNode() 方法,根据双向链表的顺序迭代键值对。

    final LinkedHashMap.Entry<K,V> nextNode() {LinkedHashMap.Entry<K,V> e = next;if (modCount != expectedModCount)throw new ConcurrentModificationException();if (e == null)throw new NoSuchElementException();current = e;// next 为双向链表的下一个节点next = e.after;return e;}

jdk1.8 源码阅读:https://github.com/zchen96/jdk1.8-source-code-read

参考资料

搞懂 Java LinkedHashMap 源码

Java8 LinkedHashMap 源码阅读相关推荐

  1. Java8 Hashtable 源码阅读

    一.Hashtable 概述 Hashtable 底层基于数组与链表实现,通过 synchronized 关键字保证在多线程的环境下仍然可以正常使用.虽然在多线程环境下有了更好的替代者 Concurr ...

  2. Java8 ArrayBlockingQueue 源码阅读

    一.什么是 ArrayBlockingQueue ArrayBlockingQueue 是 GUC(java.util.concurrent) 包下的一个线程安全的阻塞队列,底层使用数组实现. 除了线 ...

  3. Java8 PriorityQueue 源码阅读

    一.什么是 PriorityQueue 这篇文章带大家去了解一个 jdk 中不常用的数据结构 PriorityQueue(优先队列),虽然在项目里用的不多,但是它本身的设计实现还是很值得大家看一看的. ...

  4. Flume-NG源码阅读之SourceRunner,及选择器selector和拦截器interceptor的执行

    在AbstractConfigurationProvider类中loadSources方法会将所有的source进行封装成SourceRunner放到了Map<String, SourceRun ...

  5. Java8 ThreadLocal 源码分析

    可参考文章: Java8 IdentityhashMap 源码分析 IdentityhashMap 与 ThreadLocalMap 一样都是采用线性探测法解决哈希冲突,有兴趣的可以先了解下 Iden ...

  6. Rpc框架dubbo-client(v2.6.3) 源码阅读(二)

    接上一篇 dubbo-server 之后,再来看一下 dubbo-client 是如何工作的. dubbo提供者服务示例, 其结构是这样的! dubbo://192.168.11.6:20880/co ...

  7. HashMap源码阅读笔记

    HashMap是Java编程中常用的集合框架之一. 利用idea得到的类的继承关系图可以发现,HashMap继承了抽象类AbstractMap,并实现了Map接口(对于Serializable和Clo ...

  8. MyBatis 源码阅读 -- 核心操作篇

    核心操作包是 MyBatis 进行数据库查询和对象关系映射等工作的包.该包中的类能完成参数解析.数据库查询.结果映射等主要功能.在主要功能的执行过程中还会涉及缓存.懒加载.鉴别器处理.主键自增.插件支 ...

  9. 3000门徒内部训练绝密视频(泄密版)第3课:Scala中函数式编程彻底精通及Spark源码阅读

    Scala中函数式编程彻底精通及Spark源码阅读 函数可以不依赖于类,函数可以作为函数的参数,函数可以作为函数的返回值 =>表明对左面的参数进行右面的加工 函数赋值给变量需要在函数名后面加空格 ...

最新文章

  1. 第二十七讲 微分方程组解的图像
  2. Combobox 控件绑定数据
  3. iisnode默认不支持PUT和DELETE的解决
  4. 【转】Beagleboard:BeagleBoneBlack
  5. 【华为云技术分享】GitHub联合开发
  6. uwsgi+django在ubuntu下命令部署亲测ok
  7. JavaEE 支付宝支付
  8. 2019年保研夏令营复试经验分享(浙大软件/南大软件/南航计算机)
  9. MYSQL学习心得6
  10. 对话独角兽得物(毒)App CTO 陈思淼:组建技术团队的十件事
  11. 4. 利用MySQL Shell安装部署MGR集群 | 深入浅出MGR
  12. 生成对抗网络(GAN)论文原文详解
  13. SSH——Hibernate初学者之旅(五)
  14. ❤️ 炒 股 实 战丨原 地 起 飞 ❤️
  15. buff系统 游戏中_原神buff状态有哪些 buff状态系统解析
  16. 夜明け前より瑠璃色な 攻略
  17. c语言三角形判定条件,c语言判定三角形的各种类型——请大家指点
  18. Linux KVM环境搭建,以及创建kvm虚拟机
  19. 随身理财专家“挖财”推iPad应用,新增帐号对比功能
  20. 软件系统的服务器环境,服务器软件环境操作系统

热门文章

  1. 云原生系列「五」我为啥又看上了serviceMesh?
  2. oracle不足位数补0
  3. Go——从文件路径解析解析GAVC坐标解决方案
  4. Rightmost Digit
  5. Middle of the Contest
  6. 集训队脱单大法:这是一道只能由学姐我自己出数据的水题
  7. 【算法学习笔记】堆排序和归并排序、其他几种排序的代码实现、比较和应用(习题)
  8. SQL解析器的性能测试
  9. GIS创新实践【实验1】郑州市地图制作与发布
  10. Cookie和Session-学习笔记03【Session快速入门、Session细节】