集合系列 Map(十三):LinkedHashMap
我们之前说过 LinkedHashMap 是在 HashMap 的基础上,增加了对插入元素的链表维护。那么其到底是怎么实现的呢?今天这篇文章就带我们来一探究竟。
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
LinkedHashMap 的声明比较简单,继承了 HashMap 类,实现了 Map 接口。
原理
我们将从类成员变量、构造方法、核心方法、扩容机制几个方向介绍 HashMap 的原理。
类成员变量
// 链表头节点
transient LinkedHashMap.Entry<K,V> head;
// 链表尾节点
transient LinkedHashMap.Entry<K,V> tail;
// 通过iterator访问时的顺序,true表示按照访问顺序,false表示按照插入顺序。
final boolean accessOrder;
可以看到在 LinkedHashMap 的类成员变量中增加了 head 和 tail 两个变量,从而实现了对插入元素的链表维护。而这里的 accessOrder 则表示遍历 LinkedHashMap 时将按照什么顺序输出,这里我们先留意一下有 accessOrder 这个参数,后续会讲到。
构造方法
LinkedHashMap 一共有 5 个构造方法:
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
构造方法基本上是进行了一些类成员变量的参数设置,没有什么难懂的地方。
核心方法
LinkedHashMap 的核心方法主要有:get、put、remove、遍历。
get
LinkedHashMap 的 get 方法实现如下:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
可以看到,其直接调用了 HashMap 的 getNode 方法获取到对应的节点。但这里有一个细节需要注意:
if (accessOrder)
afterNodeAccess(e);
这里如果我们设置了 accessOrder 为 true,那么就执行 afterNodeAccess 方法。我们继续看看这个方法做了什么。
// 将节点挪到链表尾部
void afterNodeAccess(Node<K,V> e) {
LinkedHashMap.Entry<K,V> last;
// 如果 accessOrder 为true,且尾部节点不是 e 节点,那么将其挪到尾部
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)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;
}
}
其实 afterNodeAccess 方法的作用就是将我们访问到的 e 节点挪到链表尾部。还记得我们之前说到 accessOrder 变量的作用么?
// 通过iterator访问时的顺序,true表示按照访问顺序,false表示按照插入顺序。
final boolean accessOrder;
如果 accessOrder 为 true,那么表示访问时就要按照访问顺序去访问。而在 get 方法中,我们每访问一个节点,我们就会将该节点放入链表尾部,所以我们通过 iterator 访问链表时就是按照访问顺序得到的遍历(越早访问的越在后面)。
put
我们会发现 LinkedHashMap 中并没有 put 方法的实现,这是因为其直接使用了 HashMap 的 put 方法实现。
remove
LinkedHashMap 中也没有 remove 方法的实现,也是直接使用了 HashMap 的 remove 方法实现。
遍历
在将 LinkedHashMap 的遍历之前,我们先用一个例子回顾一下 LinkedHashMap 的遍历过程。
Map<String, String> hashMap = new LinkedHashMap<>();
hashMap.put("name", "tom");
hashMap.put("age", "27");
hashMap.put("address", "guangdong");
Iterator<String> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
// name,age,address
String key = iterator.next();
System.out.println(key + "," + hashMap.get(key));
}
上面的代码输出之后是:name、age、address,其按照插入顺序访问。但如果是 HashMap 的话,那么结果是:address、name、age。要弄清楚为什么那么我们就必须看看其源码是如何实现的。我们先进入 hashMap.keySet().iterator()
这块的代码看看,即 LinkedHashMap.keySet 方法:
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new LinkedKeySet();
keySet = ks;
}
return ks;
}
可以看到其返回的是一个 LinkedKeySet 对象,我们继续看看 LinkedKeySet 对象的代码。
final class LinkedKeySet extends AbstractSet<K> {
// 省略其他方法
public final Iterator<K> iterator() {
return new LinkedKeyIterator();
}
// ...省略其他方法
上面的代码很多,但我们只需要关心 iterator 方法就可以了,因为我们调用了 keySet 方法之后就调用了 iterator 方法。我们可以看到 iterator 方法返回了一个 LinkedKeyIterator 对象。
我们知道我们获取到了 Iterator 对象之后会调用两个方法,即:hasNext 方法和 next 方法。LinkedKeyIterator 类继承了 LinkedHashIterator,其 hasNext 方法调用了父类的实现,但其 next 方法则是自己实现了。
final class LinkedKeyIterator extends LinkedHashIterator
implements Iterator<K> {
public final K next() { return nextNode().getKey(); }
}
所以如果要弄清楚其访问顺序,我们需要看看其 nextNode 方法的实现。
// LinkedHashIterator.nextNode
final LinkedHashMap.Entry<K,V> nextNode() {
// 将下个节点赋值给 e
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;
}
从上面的方法我们可以看到其把 next 节点直接赋值给 e 并返回。而每一次进行 nextNode 操作都会更新 next 属性到下一个节点。我们从构造方法可以看到 LinkedHashIterator 的构造方法可以看到,next 节点首次赋值是指向了头结点。
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = null;
}
到了这里遍历的顺序就一目了然了。首先从头结点开始遍历,一直遍历到尾节点。而链表的顺序则是我们一直在维护的。默认情况下按照插入顺序排列,如果设置了 accessOrder,那么就按照访问顺序排列。每次访问到一个节点,就将其放到尾部。所以如果设置 accessOrder 为 true,那么越近访问到的节点就会越慢访问到。
总结
LinkedHashMap 相对于 HashMap 维护了一个插入元素的顺序,但其大部分的实现都直接调用了 HashMap 的实现。所以相对于 HashMap 来说,LinkedHashMap 还是比较好理解的。
- LinkedHashMap 继承了 HashMap,直接调用了 HashMap 的实现。
- LinkedHashMap 维护了插入元素的顺序,可以根据 accessOrder 来设定是按照访问顺序遍历,还是按照插入顺序遍历。
集合系列 Map(十三):LinkedHashMap的更多相关文章
- 【Java集合系列六】LinkedHashMap解析
2017-08-14 16:30:10 1.简介 LinkedHashMap继承自HashMap,能保证迭代顺序,支持其他Map可选的操作.采用双向链表存储元素,默认的迭代序是插入序.重复插入一个已经 ...
- 集合系列 Map(十二):HashMap
HashMap 是 Map 基于哈希散列算法的实现,其在 JDK1.7 中采用了数组+链表的数据结构.在 JDK1.8 中为了提高查询效率,采用了数组+链表+红黑树的数据结构.本文所有讲解均基于 JD ...
- java集合系列——Map之TreeMap介绍(九)
一.TreeMap的简介 TreeMap是一个有序的key-value集合,基于红黑树(Red-Black tree)的 NavigableMap实现.该映射根据其键的自然顺序进行排序,或者根据创建映 ...
- java集合系列——Map之HashMap介绍(八)
1.HashMap的简介 (JDK1.7.0_79版本) HashMap是基于哈希表的Map实现的的,一个Key对应一个Value,允许使用null键和null值,不保证映射的顺序,特别是它不保证该顺 ...
- java集合系列——Map介绍(七)
一.Map概述 0.前言 首先介绍Map集合,因为Set的实现类都是基于Map来实现的(如,HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的). 1:介绍 将键映射到 ...
- 集合系列 Map(十五):TreeMap
TreeMap 是 Map 集合的有序实现,其底层是基于红黑树的实现,能够早 log(n) 时间内完成 get.put 和 remove 操作. public class TreeMap<K,V ...
- 集合系列 Map(十四):WeakedHashMap
WeakedHashMap 也是 Map 集合的哈希实现,但其余 HashMap 的不同之处在于.其每个节点的 value 引用是弱引用,可以方便 GC 回收. public class WeakHa ...
- Java 集合系列之五:Map基本操作
1. Java Map 1. Java Map 重要观点 Java Map接口是Java Collections Framework的成员.但是它不是Collection 将键映射到值的对象.一个映射 ...
- 【集合系列】- 深入浅出的分析 Set集合
一.摘要 关于 Set 接口,在实际开发中,其实很少用到,但是如果你出去面试,它可能依然是一个绕不开的话题. 言归正传,废话咱们也不多说了,相信使用过 Set 集合类的朋友都知道,Set集合的特点主要 ...
随机推荐
- sqlserver查询(子查询,全连接,等值连接,自然连接,左右连,交集,并集,差集)
--部门表 create table dept( deptno int primary key,--部门编号 dname ),--部门名 loc )--地址 ); --雇员表 create table ...
- ajax规范写法
$.ajax({ url: "http://1.1.1.1/api/v2/user/inviters", //接口 type: "post", //GET或PO ...
- v-bind和v-model的本质区别和作用域
每篇一句 一场寂寞凭谁诉.算前言,总轻负. Vue视图数据展示方式和彼此的区别: {{插值表达式}} {{}}插值表达式里面 只能写表达式,不能写语句 文本输出,不会解析标签 不能作用在标签的属性上, ...
- MySql CPU彪高到百分之1000的排查思路
You need to enable JavaScript to run this app. 原文内容来自于LZ(楼主)的印象笔记,如出现排版异常或图片丢失等情况,可查看当前链接:https:// ...
- nginx反向代理 和部分优化
准备环境 : 两台web服务 安装http 写入文档 并启动 yum -y install httpd echo "192.168.2.100" > /var/ ...
- 机器学习-Python 01
机器学习中最常用最流行的语言工具现阶段应该是Python, 这篇文章主要介绍一些常用的Python语法知识.本篇博文适合那些有其他语言基础的程序员们,如果一点基础都没有,我建议先跳过.博主以前是做移动 ...
- Spring Data初步--整合Hibernate
Spring Data课程中的技术介绍 Hibernate: Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,它将 pojo 与数据库表建立映射关系 ...
- 设计模式GOF23(结构型模式:代理模式,适配模式,桥接模式,组合模式,装饰模式,外观模式,享元模式)
结构型模式: – 分类: • 适配器模式.代理模式.桥接模式.装饰模式.组合模式.外观模式.享元模式 – 核心作用:是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题. 结构 ...
- CoderForces Round526 (A~E)题解
A. The Fair Nut and Elevator time limit per test 1 second memory limit per test 256 megabytes input ...
- Selenium之xpath绝对路径表示法
xpath写法: 绝对路径:以/开始,逐个增加节点用/分割 特点:不能跨级.类似css中的直接子元素选择器 相对路径:用两个斜杠 // 如 //div//p//a 通配符:xpath也有 ...