05_LinkedHashMap
一. 基本原理和优缺点
LinkedHashMap能记录你插入元素的顺序,在遍历时,能按照插入的顺序给你遍历出来。
LinkedHashMap是HashMap的子类,所以基本的操作与hashmap类似。不过呢,在插入、删除、替换key-value对的时候,LinkedHashMap会维护一个链表结构,专门用来记录key-value对的顺序。当我们遍历LinkedHashMap时,就能按照顺序把key-value来遍历出来。
所以啊,别看LinkedHashMap的名字中带有Linked,其实它的底层仍然是数组实现的。
当删除元素时,它会在双向链表的尾部删除节点,当查询元素时,实际上是在迭代双向链表,从头节点开始迭代。用于维护双向链表的顺序。
二. 源码分析
2.1 put(K key, V value) 初次插入
绝大部分代码逻辑与hashmap完全一样,只不过,LinkedHashMap自己重写了newNode()方法。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
将节点封装成了LinkedHashMap.Entry对象,然后使用linkNodeLast()挂载节点。
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
这里可以看到,LinkedHashMap内部维护了两个指针head和tail,分别指向顺序链表的头结点和尾结点。
每当我们向LinkedHashMap内新增一个key-value,就会在顺序链表的末尾挂载一个顺序节点。这个挂载的过程其实就是对双向链表新增一个节点。
2.2 put(K key, V value) 覆盖已经存在的key
如果LinkedHashMap中已经插入了key1-value1,此时我们插入key1-value2,会产生什么后果呢?
答案是,key1在LinkedHashMap内的插入顺序保持不变,但是value被覆盖。
关于覆盖的操作,由于我们熟悉HashMap的源码,所以立刻可以锁定到hashmap的putVal()内如下代码块:
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
如果在插入之前,能找到这个key对应的节点,那么就用新的value覆盖这个节点内的旧value,接着执行afterNodeAccess(e)。
注意,LinkedHashMap重写了afterNodeAccess(e)。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.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;
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;
}
}
LinkedHashMap有一个非常重要的参数,accessOrder ,它可以通过入参,经过构造函数传入LinkedHashMap。accessOrder默认等于false,这就意味着,你覆盖或者查询这个key,都不会改变key在链表里的顺序。当它等于true时,就截然相反了,每当你覆盖或者查询这个key,LinkedHashMap就会把这个key挪动到顺序链表的末尾。
2.3 remove
大部分的逻辑仍然走的是hashmap的removeNode(),只不过LinkedHashMap重写了afterNodeRemoval( )。
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}