我们在使用HashMap的过程中会发现一个问题,对于这个集合,我们好像没有办法让其按照我们想要的顺序输出。
针对这个需求,就诞生了LinkedHashMap。
LinkedHashMap集成了HashMap,然后有两个自己的独特成员:
private transient Entry<K,V> header; private final boolean accessOrder;
LinkedList是一个双向链表,header是其头部,是一个初始化为-1的节点,这也是一个链表常见的技巧。
void init() { header = new Entry<>(-1, null, null, null); header.before = header.after = header; }
而这位accessOrder同学就是主角啦!
对LinkedHashMap初始化时,可以选择是否初始化这个成员,
public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } public LinkedHashMap() { super(); accessOrder = false; } public LinkedHashMap(Map<? extends K, ? extends V> m) { super(m); accessOrder = false; } public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
如果不初始化,则默认是false,此时保持放入元素先后的顺序不变化,即按照你插入的顺序排列和输出。
如果选择初始化为true,则按照访问顺序排列。
从get操作中可以验证这个结论
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的方法:
void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } }
如果LinkedHashMap的accessOrder为true,就先remove,然后添加到双向链表的尾部(双向链表header的before节点就是末尾节点),这样就完成了按访问顺序排列
至于Entry内部的操作,例如添加、删除之类的,就是简单的双向链表操作的方法,直接上代码了,折叠之:
private static class Entry<K,V> extends HashMap.Entry<K,V> { // These fields comprise the doubly linked list used for iteration. Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } /** * Removes this entry from the linked list. */ private void remove() { before.after = after; after.before = before; } /** * Inserts this entry before the specified existing entry in the list. */ private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; } /** * This method is invoked by the superclass whenever the value * of a pre-existing entry is read by Map.get or modified by Map.set. * If the enclosing Map is access-ordered, it moves the entry * to the end of the list; otherwise, it does nothing. */ 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(); } }
上面看的是访问节点,那么添加节点呢?
void addEntry(int hash, K key, V value, int bucketIndex) { super.addEntry(hash, key, value, bucketIndex); // Remove eldest entry if instructed Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } }
直接继承HashMap的添加节点方法,但下面又调用了一个删除最老节点的操作,看一下:
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }
咦?那这样不是永远都返回false么?哪还有什么意义?
当然有意义,我们可以重写这个函数,为期设定删除最老节点的条件,例如size()>100之类的。
到这里,应该就意识到了,这个LinkedHashMap好像可以实现鼎鼎大名的LRU缓存算法吧?答对了,不过这个在以后细说。
好了,LinkedHashMap的分析暂时就到这里。