这篇文章只志在搞懂一个问题,LinkedHashMap是怎么实现顺序遍历HashMap的呢?
先来看下LinkedHashMap的基本结构。
- public class LinkedHashMap<K,V>
- extends HashMap<K,V>
- implements Map<K,V>
- {/****/}
LinkedHashMap,继承了HashMap,说明结构与HashMap一致。
从上一篇文章可以知道HashMap之所以不能顺序遍历,是因为它的插入顺序就是无序的。但是LinkedHashMap却实现了顺序遍历,它是怎么实现的呢?
想要搞清楚LinkedHashMap是怎么实现顺序遍历的,直接先看下它是如何实现迭代器的。
图中LinkedHashMap自己实现了几个迭代器。LinkedEntryIterator,LinkedHashIterator,LinkedKeyIterator,LinkedValueIterator。
其中LinkedHashIterator是基类,LinkedEntryIterator(遍历Map.Entry,即每一个key,value)和LinkedKeyIterator(遍历Key),LinkedValueIterator(遍历value)都继承了LinkedHashIterator。
先来看下基类。
- // LinkedHashMap.java
- abstract class LinkedHashIterator {
- LinkedHashMapEntry<K,V> next;
- LinkedHashMapEntry<K,V> current;
- int expectedModCount;
-
- LinkedHashIterator() {// 初始化next
- next = head;
- expectedModCount = modCount;
- current = null;
- }
-
- public final boolean hasNext() {// 遍历的结束条件
- return next != null;
- }
-
- // nextNode方法没什么特别的,就是简单的遍历链表而已
- // 唯一需要注意的是,这里nextNode返回的是LinkedHashMapEntry,
- // 而不是HashMap的Node
- final LinkedHashMapEntry<K,V> nextNode() {// 遍历链表
- LinkedHashMapEntry<K,V> e = next;
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- if (e == null)
- throw new NoSuchElementException();
- current = e;
- next = e.after;
- return e;
- }
-
- public final void remove() {
- Node<K,V> p = current;
- if (p == null)
- throw new IllegalStateException();
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- current = null;
- K key = p.key;
- removeNode(hash(key), key, null, false, false);
- expectedModCount = modCount;
- }
- }
- // 通用调用了nextNode的方法
- final class LinkedKeyIterator extends LinkedHashIterator
- implements Iterator<K> {
- public final K next() { return nextNode().getKey(); }
- }
-
- final class LinkedValueIterator extends LinkedHashIterator
- implements Iterator<V> {
- public final V next() { return nextNode().value; }
- }
-
- final class LinkedEntryIterator extends LinkedHashIterator
- implements Iterator<Map.Entry<K,V>> {
- public final Map.Entry<K,V> next() { return nextNode(); }
- }
从LinkedKeyIterator和LinkedValueIterator和LinkedEntryIterator迭代器的实现可以看出,实现遍历都是调用的基类的nextNode方法。
而基类的nextNode方法,只是做了简单的链表遍历。而且是一个完整的链表,跟HashMap中为了解决哈希冲突而构建的链表不同。初次之外,返回的也是LinkedHashMapEntry,而不是HashMap中的Node。
所以,可以知道秘密实际就在LinkedHashMapEntry当中。
下面看下LinkedHashMapEntry做了什么?
- static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
- LinkedHashMapEntry<K,V> before, after;
- LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
- super(hash, key, value, next);
- }
- }
LinkedHashMapEntry也特别简单,就是存储了一个before,after的指针。
从这里其实可以猜测,LinkedHashMap实际上应该是在put的过程中,顺便构建了一个双链表。
这样,在插入,查询的时候,可以使用HashMap的快速,而在遍历的时候,就根据这个链表进行遍历。简直完美!
->_->唯一的不足就是耗费了空间而已。但是哪有这么多两全其美的事情呢~
- // LinkedHashMap.java
- // head的赋值,newNode在HashMap的putVal方法也会调用
- 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);// 在创建一个新建节点的同时,构建p和链表的最后一个节点的关系:p.before = last;last.after = p;构建双链表
- return p;
- }
-
- // 构建双链表
- 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;
- }
- }
LinkedHashMap没有重写putVal方法,所以看HashMap的putVal方法可以知道newNode是用来创建一个新节点的。
所以到这里就可以得出结论了。
LinkedHashMap在HashMap的基础上实现了记录顺序的功能。实现的方式是,重写了newNode方法,返回了一个新的数据结构:LinkedHashMapEntry。
LinkedHashMapEntry记录了prev,和next Entry。newNode方法会在put()方法的时候被调用,也就是,每次向HashMap添加元素的时候,就会记录添加的这个节点的prev和prev的next。