前面已经学习了HashMap源码和Hashtable源码,今天开始学习LinkedHashMap。参考的JDK版本为1.8。
LinkedHashMap继承了HashMap,是Map接口的哈希表和链接列表实现。哈希表的功能通过继承HashMap实现了。LinkedHashMap还维护着一个双重链接链表。此链表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。本文主要讲解双重链接链表的部分,看看它是如何实现有序性的。
数据结构
在分析LinkedHashMap源码之前,有必要了解LinkedHashMap的数据结构,否则很难理解下面的内容。
从上图中可以很清楚的看到,HashMap的数据结构是数组+链表+红黑树(since JDK1.8)+ 双重链接列表。数组+链表+红黑树的部分请参考HashMap源码一文中关于数据结构的讲解,HashMap和LinkedHashMap在这部分的实现是几乎相同的。LinkedHashMap为每个Entry添加了前驱和后继,构成了一个双向循环链表,每次向linkedHashMap插入键值对,除了将其插入到哈希表的对应位置之外,还要将其插入到双向循环链表的尾部。
部分顶部注释
Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map. (A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)
大意为LinkedHashMap是Map的哈希表和链接列表的实现,具有可预知的迭代顺序。LinkedHashMap和HashMap的不同之处在于它包含一个贯穿于所有entry的双重链接列表。双重链接列表定义了迭代顺序,默认是插入顺序。值得注意的是,如果一个key被重插入,插入顺序不受影响。
层次结构图
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
从中我们可以了解到:
- LinkedHashMap<K,V>:HashMap是以key-value形式存储数据的
- extends HashMap<K,V>:继承了HashMap,哈希表部分的功能和HashMap相似。
- implements Map<K,V>:实现了Map。HashMap已经继承了Map接口,为什么LinkedHashMap还要实现Map接口呢?仔细看过Java容器其他源码的朋友会发现,不仅仅LinkedHashMap这样做,其他实现类也经常这样做。网上的一些看法是这样做可以直观地表达出LinkedHashMap实现了Map。如果大家有其他看法,欢迎留言。
说到直观地展示出一个类的继承实现结构,eclipse的类层次结构图就可以实现这个功能。下图是LinkedHashMap类结构层次图
如何查看类层次结构图可以参考我写过的一篇文章:
域
/**
* 双向循环链表的头结点
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* 双向循环链表的尾结点
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* 迭代顺序。
* true代表按访问顺序迭代
* false代表按插入顺序迭代
*
* @serial
*/
final boolean accessOrder;
这里有必要看下LinkedHashMap的Entry的定义。LinkedHashMap的Entry继承了HashMap的Node,并且每个entry都包含前指针和后指针,这是双向循环链表的特点。
/**
* LinkedHashMap的Entry继承了HashMap的Node
*/
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);
}
}
私有方法
linkNodeLast( LinkedHashMap.Entry<K,V> p)
/**
* 将指定entry插入到双向链表末尾
*/
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
//尾指针执行p
tail = p;
//如果旧的尾节点指向null,意味着双向循环链表为空,这时头尾指针都要指向p
if (last == null)
head = p;
else {
//否则将p插入到旧尾节点的后面
p.before = last;
last.after = p;
}
}
transferLinks( LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst)
// 将src替换为dst
private void transferLinks(LinkedHashMap.Entry<K,V> src,
LinkedHashMap.Entry<K,V> dst) {
LinkedHashMap.Entry<K,V> b = dst.before = src.before;
LinkedHashMap.Entry<K,V> a = dst.after = src.after;
//如果src的前指针指向null,说明src为头节点,这时将dst替换为头节点即可
if (b == null)
head = dst;
else//否则,将dst的前指针指向的节点的后指针指向dst
b.after = dst;
//如果src的后指针指向null,说明src为尾节点,这时将dst替换为尾节点即可
if (a == null)
tail = dst;
else//否则,将dst的后指针指向的节点的前指针指向dst
a.before = dst;
}
重写HashMap的方法
reinitialize()
//将linkedHashMap重置到初始化的