一. 前语
A8站源码交易平台
先看一个比方,我们想在页面展示一周内的消费改动情况,用echarts面积图进行展示。如下:

我们在后台将数据结构结束
HashMap map = new HashMap<>();
map.put("星期一", 40);
map.put("星期二", 43);
map.put("星期三", 35);
map.put("周四", 55);
map.put("星期五", 45);
map.put("星期六", 35);
map.put("周日", 30);
但是页面上一展示,发现并非如此,我们打印出来看,发现次第并非我们所想,先put进去的先get出来
for (Map.Entry
entry : map.entrySet()){
System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}
/**
* 作用如下:
* key: 星期二, value: 40
* key: 星期六, value: 35
* key: 星期三, value: 50
* key: 周四, value: 55
* key: 星期五, value: 45
* key: 周日, value: 65
* key: 星期一, value: 30
*/
那么怎样保证预期展示作用如我们所想呢,这个时分就需要用到LinkedHashMap实体。A8站源码交易平台
二. 初识LinkedHashMap
首要我们把上述代码用LinkedHashMap进行重构
LinkedHashMap map = new LinkedHashMap<>();
map.put("星期一", 40);
map.put("星期二", 43);
map.put("星期三", 35);
map.put("周四", 55);
map.put("星期五", 45);
map.put("星期六", 35);
map.put("周日", 30);
for (Map.Entry entry : map.entrySet()){
System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}
这个时分,作用正如我们所预期
key: 星期一, value: 40
key: 星期二, value: 43
key: 星期三, value: 35
key: 周四, value: 55
key: 星期五, value: 45
key: 星期六, value: 35
key: 周日, value: 30
LinkedHashMap继承了HashMap类,是HashMap的子类,LinkedHashMap的大多数方法的结束直接运用了父类HashMap的方法,关于HashMap在前面的章节现已讲过了,《HashMap原理(一) 概念和底层架构》,《HashMap原理(二) 扩容机制及存取原理》。
LinkedHashMap可以说是HashMap和LinkedList的调团体,既运用了HashMap的数据结构,又借用了LinkedList双向链表的结构(关于LinkedList可参看Java集结 LinkedList的原理及运用),那么这样的结构怎样结束的呢,我们看一下LinkedHashMap的类结构

我们看到LinkedHashMap中定义了一个Entry静态内部类,定义了5个结构器,一些成员变量,如head,tail,accessOrder,并继承了HashMap的方法,一同结束了一些迭代器方法。我们先看一下Entry类
static class Entry extends HashMap.Node {
Entry before, after;
Entry(int hash, K key, V value, Node next) {
super(hash, key, value, next);
}
}
A8站源码交易平台我们看到这个静态内部类很简单,继承了HashMap的Node内部类,我们知道Node类是HashMap的底层数据结构,结束了数组+链表/红黑树的结构,而Entry类保留了HashMap的数据结构,一同通过before,after结束了双向链表结构(HashMap中Node类只需next特征,并不具有双向链表结构)。那么before,after和next毕竟什么关系呢。

看上面的结构图,定义了头结点head,当我们调用迭代器进行遍历时,通过head初步遍历,通过before特征可以不断找到下一个,直到tail尾结点,然后结束次第性。而在同一个hash(在上图中表现了同一行)链表内部after和next作用是相同的。不同点在于before和after可以联接不同hash之间的链表。
前面我们发现数据结构现已完全支撑其次第性了,接下来我们再看一下结构方法,看一下比起HashMap的结构方法是否有不同。
// 结构方法1,结构一个指定初始容量和负载因子的、按照刺进次第的LinkedList
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
// 结构方法2,结构一个指定初始容量的LinkedHashMap,取得键值对的次第是刺进次第
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
// 结构方法3,用默许的初始化容量和负载因子创建一个LinkedHashMap,取得键值对的次第是刺进次第
public LinkedHashMap() {
super();
accessOrder = false;
}
// 结构方法4,通过传入的map创建一个LinkedHashMap,容量为默许容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的较大者,装载因子为默许值
public LinkedHashMap(Map m) {
super(m);
accessOrder = false;
}
// 结构方法5,依据指定容量、装载因子和键值对坚持次第创建一个LinkedHashMap
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
我们发现除了多了一个变量accessOrder之外,并无不同,此变量毕竟起了什么作用?
通过注释发现该变量为true时access-order,即按访问次第遍历,假设为false,则标明按刺进次第遍历。默许为false,在哪些地方运用到该变量了,一同怎样了解?我们可以看下面的方法介绍
二. put方法
前面我们说到LinkedHashMap的put方法沿用了父类HashMap的put方法,但我们也说到了像LinkedHashMap的Entry类就是继承了HashMap的Node类,相同的,HashMap的put方法中调用的其他方法在LinkedHashMap中现已被重写。我们先看一下HashMap的put方法,这个在《HashMap原理(二) 扩容机制及存取原理》中现已有说明,我们首要注重于其间的不同点。A8站源码交易平台
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node
[] tab; Node p; int n, i;
/**
* 假设其时HashMap的table数组还未定义或许还未初始化其长度,则先通过resize()进行扩容,
* 回来扩容后的数组长度n
*/
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//通过数组长度与hash值做按位与&运算得到对应数组下标,若该方位没有元素,则new Node直接将新元素刺进
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//否则该方位现已有元素了,我们就需要进行一些其他操作
else {
Node e; K k;
//假设刺进的key和原本的key相同,则替换一下就完事了
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
/**
* 否则key不同的情况下,判别其时Node是否是TreeNode,假设是则实行putTreeVal将新的元素刺进
* 到红黑树上。
*/
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
//假设不是TreeNode,则进行链表遍历
else {
for (int binCount = 0; ; ++binCount) {
/**
* 在链表毕竟一个节点之后并没有找到相同的元素,则进行下面的操作,直接new Node刺进,
* 但条件判别有或许转化为红黑树
*/
if ((e = p.next) == null) {
//直接new了一个Node
p.next = newNode(hash, key, value, null);
/**
* TREEIFY_THRESHOLD=8,因为binCount从0初步,也就是链表长度逾越8(包括)时,
* 转为红黑树。
*/
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
/**
* 假设在链表的毕竟一个节点之前找到key值相同的(和上面的判别不冲突,上面是直接通过数组
* 下标判别key值是否相同),则替换
*/
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
//onlyIfAbsent为true时:当某个方位现已存在元素时不去掩盖
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//毕竟判别临界值,是否扩容。
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
1. newNode方法A8站源码交易平台
首要:LinkedHashMap重写了newNode()方法,通过此方法保证了刺进的次第性。

A8站源码交易平台
2. afterNodeAccess方法
其次:关于afterNodeAccess()方法,在HashMap中没给具体结束,而在LinkedHashMap重写了,目的是保证操作过的Node节点永远在毕竟,然后保证读取的次第性,在调用put方法和get方法时都会用到。


A8站源码交易平台
我们前面说到的linkNodeLast(Entry e)方法和现在的afterNodeAccess(Node e)都是将传入的Node节点放到毕竟,那么它们的运用场景怎样呢?
在前面说明HashMap时,说到了HashMap的put流程,假设在对应的hash方位上还没有元素,那么直接new Node()放到数组table中,这个时分对应到LinkedHashMap中,调用了newNode()方法,就会用到linkNodeLast(),将新node放到毕竟,而假设对应的hash方位上有元素,进行元素值的掩盖时,就会调用afterNodeAccess(),将原本或许不是毕竟的node节点拿到了毕竟。如
LinkedHashMap map = new LinkedHashMap<>(16, 0.75f, true);
map.put("1月", 20);
//此时就会调用到linkNodeLast()方法,也会调用afterNodeAccess()方法,但会被阻遏在
//if (accessOrder && (last = tail) != e) 之外
map.put("2月", 30);
map.put("3月", 65);
map.put("4月", 43);
//这时不会调用linkNodeLast(),会调用afterNodeAccess()方法将key为“1月”的元素放到毕竟
map.put("1月", 35);
//这时不会调用linkNodeLast(),会调用afterNodeAccess()方法将key为“2月”的元素放到毕竟
map.get("2月");
//调用打印方法
for (Map.Entry entry : map.entrySet()){
System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}
作用如下:
key: 3月, value: 65
key: 4月, value: 43
key: 1月, value: 35
key: 2月, value: 30
而假设是实行下面这段代码,将accessOrder改为false
LinkedHashMap map = new LinkedHashMap<>(16, 0.75f, false);
map.put("1月", 20);
//此时就会调用到linkNodeLast()方法,也会调用afterNodeAccess()方法,但会被阻遏在
//if (accessOrder && (last = tail) != e) 之外
map.put("2月", 30);
map.put("3月", 65);
map.put("4月", 43);
//这时不会调用linkNodeLast(),会调用afterNodeAccess()方法将key为“1月”的元素放到毕竟
map.put("1月", 35);
map.get("2月");
//调用打印方法
for (Map.Entry entry : map.entrySet()){
System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
}
作用如下:
key: 1月, value: 35
key: 2月, value: 30
key: 3月, value: 65
key: 4月, value: 43
A8站源码交易平台
我们看到差异了吗,accessOrder为false时,你访问的次第就是按照你第一次刺进的次第;而accessOrder为true时,你任何一次的操作,包括put、get操作,都会改动map中已有的存储次第。
3. afterNodeInsertion方法
我们看到在LinkedHashMap中还重写了afterNodeInsertion(boolean evict)方法,它的目的是移除链表中最老的节点方针,也就是其时在头部的节点方针,但实际上在JDK8中不会实行,因为removeEldestEntry方法一向回来false。看源码:
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry eldest) {
return false;
}
三. get方法
LinkedHashMap的get方法与HashMap中get方法的不同点也在于多了afterNodeAccess()方法
public V get(Object key) {
Node e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
A8站源码交易平台 在这儿就不再多讲了,getNode()方法在HashMap章节现已讲过,而前面刚把afterNodeAccess讲了。
四.remove方法
remove方法也直接运用了HashMap中的remove,在HashMap章节并没有说明,因为remove的原理很简单,通过传递的参数key计算出hash,据此可找到对应的Node节点,接下来假设该Node节点是直接在数组中的Node,则将table数组该方位的元素设置为node.next;假设是链表中的,则遍历链表,直到找到对应的node节点,然后建立该节点的上一个节点的next设置为该节点的next。
LinkedHashMap重写了其间的afterNodeRemoval(Node e),该方法在HashMap中没有具体结束,通过此方法在删去节点的时分调整了双链表的结构。
void afterNodeRemoval(Node e) {
LinkedHashMap.Entry p = (LinkedHashMap.Entry)e, b = p.before, a = p.after;
//将待删去节点的before和after都设置为null
p.before = p.after = null;
/**
* 假设节点b为null,标明待删去节点p为头部节点,该节点拿掉后,该节点的下一个节点a就为头部节点head
* 否则设置待删去节点的上一个节点b的after特征为节点a
*/
if (b == null)
head = a;
else
b.after = a;
/**
* 假设节点a为null,标明待删去节点p为尾部节点,该节点拿掉后,该节点的上一个节点a就为尾部节点tail
* 否则设置待删去节点的下一个节点a的before特征为节点b
*/
if (a == null)
tail = b;
else
a.before = b;
}
A8站源码交易平台
五. 总结
LinkedHashMap运用的也较为一再,它依据HashMap,用于HashMap的特征,又增加了双链表的结构,然后保证了次第性,本文首要从源码的角度分析其怎样保证次第性,accessOrder的说明,以及常用方法的阐释。
A8站源码交易平台