上一节我们说到HashMap的存取原理,并一步步地分析了其主要的成员变量、构造函数以及体现其算法的put,get方法。本文将继续分析HashMap的子类—LindedHashMap,来解决上一节提到的,如何保证迭代顺序和插入顺序的一致性的问题。
public class LinkedHashMap
LinkedHashMap直接继承于HashMap,且其内部属性和存取算法与HashMap基本一致。
那么为什么LinkedHashMap可以做到保证迭代顺序和插入顺序一致呢? 这就要归功于LinkedHashMap中的一个数据结构—Entry header了,header是一个双向链表中的首节点,该双向链表保存了entry插入的顺序,在迭代时,实际上是在迭代该双向链表。每次调用put()方法时,第一步和HashMap的put()方法一致,第二步就是将该元素的before节点指向header的before节点,after节点指向header,将header的before节点指向该元素,以完成双向链表的记录顺序。
/**
* The head of the doubly linked list.
*/
private transient Entry header;
双向链表的头结点。
/**
* The iteration ordering method for this linked hash map: true
* for access-order, false for insertion-order.
*
* @serial
*/
private final boolean accessOrder;
标识迭代顺序的标志,true表示按照访问顺序进行迭代(),false表示按照插入的顺序进行迭代。
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(Map extends K, ? extends V> m) {
super(m);
accessOrder = false;
}
@Override
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
构造函数和HashMap的基本一致,只是多了将accessOrder设为false的操作;而且可以将一个HashMap的对象强转为LinkedHashMap的对象。
而且,LinkedHashMap重写了父类的钩子函数–init(),将双向链表的头结点初始化,并将其before、after节点指向了自己,其实就是对双向链表进行初始化。
打开LinkedHashMap的源码,Ctrl+O,输入put,你会惊奇的发现这个类里面并没有put方法,说明LinkedHashMap没有重写父类的put()方法,只是继承了父类的put()方法。那么问题来了,如果没有重写put()方法,LinkedHashMap是如何在插入数据时将数据记录到双向链表中的呢?
答案是:LinkedHashMap重写了父类的addEntry()和createEntry()方法,并在createEntry()方法中将元素插入到双向链表的尾部。
void addEntry(int hash, K key, V value, int bucketIndex) {
//调用父类的addEntry方法
super.addEntry(hash, key, value, bucketIndex);
//实现移除最近最少使用的元素,不过从源码上看,该方法返回的值一直为false
// Remove eldest entry if instructed
Entry eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry old = table[bucketIndex];
Entry e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);//将刚加入桶中的元素添加到记录插入顺序的双向链表中的尾节点。
size++;
}
//双向链表尾插法
private void addBefore(Entry existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
LikedHashMap重写了父类的get()方法,第一步调用父类的getEntry()方法,获取到相应的entry元素,第二步调用重写的recordAccess()方法,来记录访问顺序;
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
只有当accessOrder为true时,即指定迭代顺序为访问顺序时,才会将当前元素插入到双向链表的头结点。
LinkedHashMap作为HashMap的子类,其实现方式十分相似,区别在于,LinkedHashMap可以保证迭代顺序和插入顺序的一致,当你发现从数据库里读取的数据和返回的数据顺序不一致时 就要考虑使用LinkedHashMap了。