LinkedHashMap
- LinkedHashMap继承HashMap,其结构与HashMap相似
- 它保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。
我们知道HashMap有一个问题,就是它的遍历顺序是不确定的。但是有些场景,我们需要一个有序的Map。为解决这一问题,LinkedHashMap诞生了。它通过维护一个运行于所有条目的双向链表,虽然增加了时间和空间上的开销,但是保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。
源码分析
LinkedHashMap继承HashMap
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
属性
transient LinkedHashMap.Entry<K,V> head;//指向最久访问的节点或最先插入的节点
transient LinkedHashMap.Entry<K,V> tail;//指向最近访问的或最近插入的节点
final boolean accessOrder;//是否基于访问排序
head指向最久访问的节点或最先插入的节点,tail指向最近访问的或最近插入的节点。accessOrder表示是否基于访问排序。默认为false,即插入顺序。
构造器
public LinkedHashMap(int initialCapacity, float loadFactor) {
// 调用HashMap的构造方法,其实就是初始化Entry[] table
super(initialCapacity, loadFactor);
// 这里是指是否基于访问排序,默认为false
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
可以看到LinkedHashMap的构造器就是调用HashMap的构造方法,初始化Node<K,V>[] table。然后设置是否基于访问排序。
内部类Entry
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);
}
}
我们可以看到Entry<K,V> 继承了HashMap.Node,并额外定义了两个Entry<K,V> before, after;指针,before、after是用于维护Entry插入的先后顺序的。(before指向在它之前插入的entry,afte指向在它之后插入的entry)
那么before、After是如何维护Entry插入的先后顺序的呢?
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重写了newNode方法。该方法new了一个entry然后调用了 linkNodeLast( p )方法。
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;
}
}
inkNodeLast( p )方法,使before指向在它之前插入的entry,使在它之前插入的entry的after的指向它自己。
下面我们来看看它是如何遍历的。
让我们看看它的迭代器:LinkedHashIterator
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
// next 指向head(最久访问的节点或最先插入的节点)
next = head;
expectedModCount = modCount;
current = null;
}
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
current = e;
// next 指向after(在它之后插入的entry)
next = e.after;
return e;
}
LinkedHashIterator构造器的作用就是使next指向head(最久访问的节点或最先插入的节点)。
nextNode()的作用就是使next 指向after(在它之后插入的entry)。并返回当前的entry。
总结:当我们调用迭代器或增强for循环遍历LinkedHashMap时,首先初始化LinkedHashIterator使next指向head。然后再调用nextNode()依次遍历它的下一个,直到遍历完。
从上面我们可以看到它的迭代器,遍历的是插入顺序,那么它使如何实现访问顺序的呢?
首先看下get()方法访问数据。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
我们可以看到,除了获取到对应的key外,如果accessOrder设置为true,那么就会调用afterNodeAccess(Node<K,V> e)。
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//当accessOrder为true,且当前节点不是最后一个插入的节点。
if (accessOrder && (last = tail) != e) {
//将当前节点的引用赋给p,b指向在p插入之前的节点,a指向在p之后插入的节点。
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
//b == null说明当前节点是第一个插入的节点,使 head指向第二个插入的节点。
if (b == null)
head = a;
else
// 使它前一个的after指向它的后一个。
b.after = a;
if (a != null)//说明它不是最后插入的节点
// 使它后一个的before指向它的前一个。
a.before = b;
else
//使last指向它的前一个
last = b;
if (last == null)
head = p;
else {
//将当前节点放置在链表的尾部。
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
afterNodeAccess(Node<K,V> e)就是将你访问的该节点移动到对应桶中链表的尾部。然后你再遍历的时候,尾部就是最近访问的元素。