前言

经常见的总是要分析下呢。 跟 List 的相似, 我们通过名称就可以出来, LinkedHashMap 是链式存储? 跟 LinkedList 一样吧。 但是通过源码的查看还是有点不同的, 我们总体看, LinkedHashMap 直接基础了 HashMap, 其内部存储数据的方式还是用的 HashMap 的那一套, 所以在数据存储的时候还是数据 + 链表/红黑树。 那么 Linked 是哪里得来的呢, 先说结果吧, 其实 LinkedHashMap 自己有一个 head 和 tail 指针, 然后在新增数据的时候会记录下新增数据的顺序。

源码分析

整体来讲就是重写了 HashMap 的方法, 然后记录下了插入的顺序等, 双向链表, 所有的操作无非就是链表的操作, 这里不再多说了。 主要分析下主要的两个流程吧。 其中一个是在新增的时候链表是怎么形成的, 第二个呢可以观察到构造方法里有一个参数是 accessOrder, 意思是会按照操作进行排序, 比如我修改了一个已经存在的值, 那么这个节点就会放到链表的结尾。

链表的形成

1
2
3
4
5
6
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMapEntry<K,V> p =
new LinkedHashMapEntry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}

这个方式是重写的 HaspMap 里的方法, 其中 HashMap 是怎么处理存储数据见:HashMap 源码分析。
其中我们看下其中添加数据时的调用

1
2
3
4
5
6
7
8
9
10
11
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// ......
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// ......
afterNodeAccess(e);
}
// ......
}

可以看到如果我们插入的数据是新的数据, 那么会调用 newNode 去初始化一个新的 Node 节点, 所以 LinkedHashMap 中的 newNode 是新建时调用的。 多说一句, 如果已经存在了就会调用 afterNodeAccess 方法。 那么 LinkedHashMap 就是实现了这个方法从而来实现了 accessOrder 的处理的。
我们接着向下看

1
2
3
4
5
6
7
8
9
10
private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
LinkedHashMapEntry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}

是不是很简单, 就是如果有就拼到后面, 没有就是新的链表呗。 链表操作, 不再多说。

AccessOrder 的处理

通过上面的分析我们得知, 在已存在的数据发生改变的时候会调用 afterNodeAccess, 所以我们来看看这个方法的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<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;
}
}

其实看代码很明白了, 不再过多解释了, 就是把操作的这个节点, 放到最后。 就正如下面的这样
a(head)->b->c->d->e(tail)->null
现在修改了 c 的节点, 最后经过处理后的链表长这样
a(head)->b->d->e->c(tail)->null