文章目录
前言
在阅读这篇文章之前,我们应该已经了解了 HashMap 的底层实现。
HashMap 是一个存储键值对的集合,jdk1.8 中底层的数据结构有数组+链表+红黑树,对于 get 、 put 操作,时间复杂度是O(1)的,是不是很优秀?
但是, HashMap 插入元素之后,对它进行遍历,遍历的结点顺序是无序的,如果我们希望遍历的顺序是可预测的呢?这时, LinkedHashMap 闪亮登场!
从 LinkedHashMap 的源码中可以看到, LinkedHashMap 继承了 HashMap ,实现了 Map 接口。继承,使 LinkedHashMap 包含 HashMap 的所有成员(除了 private 成员),而且和 HashMap 具有相同的基础接口。
那么 LinkedHashMap 是如何在 HashMap 的基础上,实现可预测的迭代顺序的?
以下,我们将从 LinkedHashMap 的 Entry 结点类和部分属性、 put 操作、 get 操作和 containsValue 操作来了解它。
源码
看源码是很好的学习方式。但在之前我也看过集合类的相关源码,由于停留在能看懂、理解主要思想的地步,在实习面试的时候被问到表现的也不尽人意。所以,不仅仅是看源码,还要思考和总结,还可以多看些博客,也学习别人的思考和总结。
嗯,那我们来看源码吧。
Entry结点类和部分属性
/**
* LinkedHashMap 的 Entry 结点类继承自 HashMap 的 Node 结点
* 类,添加了 before 、 after 指针,用来维护双向链表的顺序。
* @param <K>
* @param <V>
*/
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
private static final long serialVersionUID = 3801124242820219131L;
/**
* 双向链表的头节点
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* 双向链表的尾节点
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* true - 按访问顺序
* false - 按插入顺序
*/
final boolean accessOrder;
LinkedHashMap 的结点类 Entry 继承了 HashMap 的 Node 类,多了两个属性 before 和 after。 before 是指向前一个结点的指针, after 是指向后一个结点的指针。
我动手画了画HashMap 和 LinkedHashMap 的大致存储结构,如下图。
HashMap 和 LinkedHashMap 最大的不同是什么呢?LinkedHashMap 前面多了个 Linked ?我们可不是这么表面的人。从图中可以看到,通过 after、before 指针,LinkedHashMap 另外维护了一条双向链表。
现在我们可以从双向链表的头节点 head 开始,根据 after 指针指向的结点逐个遍历,得到的是有一定顺序的序列。
我们再看回上面贴的源码, LinkedHashMap 还有一个属性 accessOrder。我们一直说到 LinkedHashMap 可以实现可预测的迭代顺序,这个顺序是指什么顺序呢?
默认情况下,LinkedHashMap 的双向链表维护的顺序是结点插入的顺序,头节点是最早插入的结点。如果我们在构造函数中传入 accessOrder 为 true,双向链表维护的是结点访问的顺序,头节点是最久未访问的结点。构造函数我就不贴代码了,我们接着看!
put操作
我们在 LinkedHashMap 中找不到 put 方法,这是因为它并没有重写父类 HashMap 的 put 方法。这里用到了一种典型的设计模式:模板方法模式。
模板方法模式,在《大话设计模式》中是这样说的:
模板方法模式,定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
LinkedHashMap 只是重写了父类 HashMap 的 putVal 方法中的某些方法。
我们来回顾下 putVal 方法的具体流程和了解 LinkedHashMap 重写的方法。
-
首先第一步判断哈希表是否为空或长度是否为0,如果是的话,调用 resize()方法。
-
找到结点对应的桶的位置,如果桶中没有结点则 new 一个结点。
这里,LinkedHashMap 重写了 newNode 方法。下面是 LinkedHashMap 重写的 newNode 方法,其中,LinkedHashMap 创建的是自己类中的 Entry 结点,并且要维护双向链表,在双向链表的尾部添加新创建的结点。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new