在上一篇隨筆中,分析了HashMap的源碼,裏面涉及到了3個鉤子函數(afterNodeAccess(e),afterNodeInsertion(evict),afterNodeRemoval(node)),用來預設給子類——LinkedHashMap調用。 node
能夠從上圖中看到,LinkedHashMap數據結構相比較於HashMap來講,添加了雙向指針,分別指向前一個節點——before和後一個節點——after,從而將全部的節點已鏈表的形式串聯一塊兒來。數據結構爲(數組 + 單鏈表 + 紅黑樹 + 雙鏈表),圖中的標號是結點插入的順序。 數組
1,LinkedHashMap結構 數據結構
LinkedHashMap繼承HashMap,因此HashMap中的非private方法和字段,均可以在LinkedHashMap直接中訪問。 函數
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> { 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); } } // 版本序列號 private static final long serialVersionUID = 3801124242820219131L; // 鏈表頭結點 transient LinkedHashMap.Entry<K,V> head; // 鏈表尾結點 transient LinkedHashMap.Entry<K,V> tail; /** * 用來指定LinkedHashMap的迭代順序, * true則表示按照基於訪問的順序來排列,意思就是最近使用的entry,放在鏈表的最末尾 * false則表示按照插入順序來 */ final boolean accessOrder; }
2,構造函數 this
LinkedHashMap提供了五種方式的構造器,全部構造函數的第一行都會調用父類構造函數,使用super關鍵字,以下 spa
構造器一: 指針
public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; }
accessOrder默認爲false,access爲true表示以後訪問順序按照元素的訪問順序進行,即不按照以前的插入順序了,access爲false表示按照插入順序訪問。 code
構造器二: blog
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); }
putMapEntries是調用到父類HashMap的函數。
構造器五:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
經過指定accessOrder的值,從而控制訪問順序。
3,LinkedHashMap.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); } }
LinkedHashMap.Entry繼承自HashMap.Node,在HashMap.Node基礎上增長了先後兩個指針域。
4,部分函數
4.1,get()函數
public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; //accessOrder爲true則表示按照基於訪問的順序來排列,意思就是最近使用的entry,放在鏈表的最末尾 //在取值後對參數accessOrder進行判斷,若是爲true,執行afterNodeAccess if (accessOrder) afterNodeAccess(e); return e.value; } //此函數執行的效果就是將最近使用的Node,放在鏈表的最末尾 void afterNodeAccess(Node<K,V> e) { LinkedHashMap.Entry<K,V> last; //僅當按照LRU原則且e不在最末尾,才執行修改鏈表,將e移到鏈表最末尾的操做 if (accessOrder && (last = tail) != e) { //將e賦值臨時節點p, b是e的前一個節點, a是e的後一個節點 LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; //設置p的後一個節點爲null,由於執行後p在鏈表末尾,after確定爲null p.after = null; //狀況一(p爲頭部):p前一個節點爲null if (b == null) head = a; else b.after = a; //狀況二(p爲尾部):p的後一個節點爲null if (a != null) a.before = b; else last = b; //狀況三(p爲鏈表裏的第一個節點) if (last == null) head = p; else { //正常狀況,將p設置爲尾節點的準備工做,p的前一個節點爲原先的last,last的after爲p p.before = last; last.after = p; } //將p設置爲尾節點 tail = p; // 修改計數器+1 ++modCount; } }
概念:
LRU(Least Recently Used): 意思就是最近讀取的數據放在最前面,最先讀取的數據放在最後面,若是這個時候有新的數據進來,那麼最後面存儲的數據淘汰。
說明一下:
正常狀況下:查詢的p在鏈表中間,那麼將p設置到末尾後,它原先的 前節點b 和 後節點a 就變成了先後節點。
狀況一:p爲頭部,前一個節點b不存在,那麼考慮到p要放到最後面,則設置p的後一個節點a爲head。
狀況二:p爲尾部,後一個節點a不存在,那麼考慮到統一操做,設置last爲b。
狀況三:p爲鏈表裏的第一個節點,head=p。
將最近使用的Node,放在鏈表的最末尾示意圖:
4.2,put()方法
LinkedHashMap的put方法調用的仍是HashMap裏的put,不一樣的是重寫了裏面的部分方法。
LinkedHashMap將其中newNode方法以及以前設置下的鉤子方法afterNodeAccess(該方法上面已說明)和afterNodeInsertion進行了重寫,從而實現了加入鏈表的目的。
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; } //把新加的節點放在鏈表的最後面。 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { //將tail給臨時變量last LinkedHashMap.Entry<K,V> last = tail; tail = p; //若沒有last,說明p是第一個節點,head=p if (last == null) head = p; else { p.before = last; last.after = p; } } //插入後把最老的Entry刪除,不過removeEldestEntry老是返回false,因此不會刪除,估計又是一個鉤子方法給子類用的 void afterNodeInsertion(boolean evict) { LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }
4.3,remove()方法
在HashMap的remove方法中也有一個鉤子方法afterNodeRemoval。
LinkedHashMap的remove方法調用的仍是HashMap裏的remove,不一樣的是重寫了裏面的部分方法。
void afterNodeRemoval(Node<K,V> e) { //記錄e的先後節點b,a LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; //p已刪除,先後指針都設置爲null,便於GC回收 p.before = p.after = null; if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b; }
4.4,transferLinks()方法
//替換節點的方法,咱們使用的replacementNode,replacementTreeNode等方法都是經過該方法實現的 private void transferLinks(LinkedHashMap.Entry<K,V> src, LinkedHashMap.Entry<K,V> dst) { LinkedHashMap.Entry<K,V> b = dst.before = src.before; LinkedHashMap.Entry<K,V> a = dst.after = src.after; if (b == null) head = dst; else b.after = dst; if (a == null) tail = dst; else a.before = dst; }
dst節點替換src節點示意圖:
5,LinkedHashMap的迭代器
abstract class LinkedHashIterator { //記錄下一個Entry LinkedHashMap.Entry<K,V> next; //記錄當前的Entry LinkedHashMap.Entry<K,V> current; //記錄是否發生了迭代過程當中的修改 int expectedModCount; LinkedHashIterator() { //初始化的時候把head給next next = head; expectedModCount = modCount; current = null; } public final boolean hasNext() { return next != 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 = 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; } }