一.概述:
LinkedHashMap是HashMap的子类,它的基本操作与HashMap相同,与之不同的是,它可以实现按照插入顺序进行排序.也可以通过在构造函数中指定参数,按照访问顺序排序(LinkedHashSet无法按照访问顺序进行排序).
二.LinkedHashMap是如何实现按照插入顺序进行排序的?
LinkedHashMap在Entry内部类中动了一点小手脚…实际上,LinkedHashMap所有的元素可以构成一个以header(Entry对象)为头的双向链表.在HashMap中有一个钩子方法init,在构造函数最后一行调用,在HashMap的实现中,init方法没有实现任何内容.而LinkedHashMap则实现了init方法,init方法的代码如下:
void init() { header = new Entry<K,V>(-1, null, null, null); header.before = header.after = header; }
LinkedHashMap对addEntry方法也做出了修改,首先它没有改变数组的数据结构,在放入一个元素的时候,依然将元素的hash值对应的索引处的引用指向该元素,将该元素的next属性指向之前该索引处引用指向的元素,不同的是,在插入一个新的元素的时候,它还维护了双向链表数据结构的插入.首先看看内部类Entry的数据结构:
private static class Entry<K,V> extends HashMap.Entry<K,V> { // 这两个引用说明entry之间是用双向链表连接起来的 Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } /** * 将这个entry从双向链表中移除 */ private void remove() { before.after = after; after.before = before; } /** * 将这个entry加到给定参数entry的前面 */ private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; } /** * 这个方法调用在当支持按照访问次数排序的时候.也是在HashMap中定义, 但没有给出具体方法,在LinkedHashMap中实现 */ void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } void recordRemoval(HashMap<K,V> m) { remove(); } }
再看看addEntry方法和createEntry方法:
void addEntry(int hash, K key, V value, int bucketIndex) { createEntry(hash, key, value, bucketIndex); // 在LinkedHashMap中,removeEldestEntry方法始终返回false Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { if (size >= threshold) resize(2 * table.length); } } void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; //维护双向链表的操作 e.addBefore(header); size++; }
可以看出调用的addBefore方法实现了维护双向链表的操作,下面列出了向一个没有任何元素的LinkedHashMap中插入元素时,内部发生的事情:
1.首先计算键值对的key值的hash值.根据hash值计算索引.
2.用传入的键值对new出一个Entry对象.由于索引处是null,那么将底层数组该索引处的引用指向该Entry对象
3.将该entry对象插入到header的前面,这时header.before是该entry,header.after也是该entry.
4.增加size,如果size>=threshold,增加容器的容积.
当继续添加元素的时候,新添加的元素都是在添加到header的前面,而header.after始终是第一个添加的元素.
下面是LinkedHashMap定义的迭代器:
private abstract class LinkedHashIterator<T> implements Iterator<T> { Entry<K,V> nextEntry = header.after; Entry<K,V> lastReturned = null; int expectedModCount = modCount; public boolean hasNext() { return nextEntry != header; } public void remove() { if (lastReturned == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); LinkedHashMap.this.remove(lastReturned.key); lastReturned = null; expectedModCount = modCount; } Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (nextEntry == header) throw new NoSuchElementException(); Entry<K,V> e = lastReturned = nextEntry; nextEntry = e.after; return e; } }
可以看出,迭代器是从header开始,不断调用header.after获取元素.直到指针又重新回到header.因此这样子的迭代顺序配合内部双向链表的数据结构保证了实现了按照插入顺序排序.
三.LinkedHashMap是如何实现按照访问次序排序的?
在LinkedHashMap中,有一个成员变量accessOrder来判断是否需要按照访问顺序排序.accessOrder可以通过,在构造函数中传入true,来指定,必须按照访问顺序排序.所谓的按照访问顺序排序是指,如果一对键值对被访问了,那么就将它移到当前遍历顺序的最后,这样,最不经常访问的键值对会排在前面.
在LinkedHashMap中,通过在get后,调用recordAccess方法,来实现按照访问顺序排序:
public V get(Object key) { Entry<K,V> e = (Entry<K,V>)getEntry(key); if (e == null) return null; e.recordAccess(this); return e.value; }
而recordAccess方法在entry内部类中实现,它是将该该entry移除双向链表,并且将该entry重新加入到header的前面(遍历时将会最后一个遍历),这样就实现了按照访问次序排序.