JDK1.8 原始碼分析(九)--LinkedHashMap

我很醜發表於2020-03-05

1. 類的定義

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
複製程式碼

從定義可以看出

  • LinkedHashMap是泛型類
  • LinkedHashMap繼承自HashMap
  • LinkedHashMap實現了Map介面

2. 構造方法

//傳入初始化容量和負載因子
public LinkedHashMap(int initialCapacity, float loadFactor) {
    	//呼叫父類的構造方法
        super(initialCapacity, loadFactor);
    	//迭代時的順序方式 accessOrder true 表示訪問順序 false 插入順序
        accessOrder = false;
    }
//傳入初始化容量
public LinkedHashMap(int initialCapacity) {
    	//呼叫父類的構造方法
        super(initialCapacity);
    	//迭代時的順序方式 accessOrder true 表示訪問順序 false 插入順序
        accessOrder = false;
    }
//預設構造方法
public LinkedHashMap() {
    	//呼叫父類預設的構造方法
        super();
    	//迭代時的順序方式 accessOrder true 表示訪問順序 false 插入順序
        accessOrder = false;
    }
//傳入一個Map物件
public LinkedHashMap(Map<? extends K, ? extends V> m) {
    	//呼叫父類預設的構造方法
        super();
    	//迭代時的順序方式 accessOrder true 表示訪問順序 false 插入順序
        accessOrder = false;
    	//加入map中的元素
        putMapEntries(m, false);
    }
//傳入初始化容量 負載因子 迭代順序方式
public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
    	//呼叫父類的構造方法
        super(initialCapacity, loadFactor);
    	//設定迭代訪問順序方式
        this.accessOrder = accessOrder;
    }
複製程式碼

從構造方法可以看出

  • LinkedHashMap 迭代時預設的順序時插入時的順序
  • 所有的構造方法都呼叫了父類HashMap的構造方法,可以指定初始化容量和負載因子

3.欄位屬性

	//序列化版本號
	private static final long serialVersionUID = 3801124242820219131L;
	//頭節點
    transient LinkedHashMap.Entry<K,V> head;
	//尾節點
    transient LinkedHashMap.Entry<K,V> tail;
	//迭代時的順序方式 accessOrder true 表示訪問順序 false 插入順序
    final boolean accessOrder;
複製程式碼

從上面可以看出

  • LinkedHashMap中的元素都是LinkedHashMap.Entry物件

  • LinkedHashMap中儲存了頭節點和尾節點

  • LinkedHashMap支援序列化,從HashMap中繼承而來

  • 可以指定迭代時的順序

    • LinkedHashMap.Entry時LikedHashMap的一個靜態內部類

      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的定義中可以看出

      • Entry是一個泛型類
      • Entry繼承自HashMap.Node
      • Entry儲存了前後節點,因此Entry是一個雙向連結串列

4.方法

containsValue 方法

//是否包含傳入的value 
public boolean containsValue(Object value) {
     	//for迴圈遍歷 從頭節點開始
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
            //獲取當前遍歷節點的值
            V v = e.value;
            if (v == value || (value != null && value.equals(v)))
                //如果當前節點的值等於查詢的值,返回true
                return true;
        }
     	//沒找到,返回false
        return false;
    }
複製程式碼

get 方法

//根據傳入的key查詢對應的vaule
public V get(Object key) {
        Node<K,V> e;
    	//通過呼叫父類HashMap的getNode方法查詢對應的節點
        if ((e = getNode(hash(key), key)) == null)
            //沒有對應的節點,返回null
            return null;
        if (accessOrder)
            //如果迭代順序是訪問順序,移動當前節點到連結串列的末尾
            afterNodeAccess(e);
    	//返回節點的value
        return e.value;
    }
複製程式碼

getOrDefault 方法

//根據傳入的key查詢對應的value,如果不存在返回預設值
public V getOrDefault(Object key, V defaultValue) {
       Node<K,V> e;
    	//通過呼叫父類HashMap的getNode方法查詢對應的節點
       if ((e = getNode(hash(key), key)) == null)
           //沒找到對應的節點,返回預設值
           return defaultValue;
       if (accessOrder)
           //如果迭代順序是訪問順序,移動當前節點到連結串列的末尾
           afterNodeAccess(e);
       //找到,返回節點的value
       return e.value;
   }
複製程式碼

afterNodeAccess 方法

void afterNodeAccess(Node<K,V> e) { // move node to last
    	//最後一個節點
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            //如果是迭代順序是訪問順序 並且 傳入的當前節點不是尾節點
            //p 當前節點的副本
            //b 當前節點的前一個節點
            //a 當前節點的後一個節點
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            //把當前節點的後一個節點引用置為null
            p.after = null;
            if (b == null)
                //如果當前節點的前一個引用位null,表示當前節點是頭節點
                //把頭節點設定為當前節點的後一個節點
                head = a;
            else
                //當前節點不是頭節點
                //把當前節點的前一個節點的後一個節點的引用指向當前節點的後一個節點,即跨過當前節點
                b.after = a;
            if (a != null)
                //如果當前節點的後一個節點不為null
                //把當前節點的後一個節點的前一個節點的引用指向當前節點的前一個節點,即跨過當前節點
                a.before = b;
            else
                //如果當前節點的後一個節點為null,表示當前節點為尾節點
                //把最後一個節點指向當前節點
                last = b;
            if (last == null)
                //如果最後一個節點為null,表示只有一個元素
                //把頭節點指向當前節點
                head = p;
            else {
                //最後一個節點不為null
                //下面兩步的作用是,把當前節點掛到最後一個節點後面
                //把當前節點的前一個節點指向最後一個節點
                p.before = last;
                //把最後一個節點的下一個節點指向當前節點
                last.after = p;
            }
            //尾節點指向當前節點
            tail = p;
            //修改統計加1
            ++modCount;
        }
    }
複製程式碼

clear 方法

//清除所有元素
public void clear() {
    	//呼叫父類的清除方法
        super.clear();
    	//再把頭節點和尾節點置為null
        head = tail = null;
    }
複製程式碼

keySet 方法

//獲取所有key的Set集合
public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            //這裡是重點,LinkedKeySet是LinkedHashMap的內部類
            //所以LinkedKeySet可以獲取LinkedHashMap所有的屬性
            //而且在使用的時候才會獲取,不會在new的時候獲取
            ks = new LinkedKeySet();
            keySet = ks;
        }
        return ks;
    }
複製程式碼

values 方法

//獲取所有value的Collection集合
public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            //這裡是重點,LinkedValues是LinkedHashMap的內部類
            //所以LinkedValues可以獲取LinkedHashMap所有的屬性
            //而且在使用的時候才會獲取,不會在new的時候獲取
            vs = new LinkedValues();
            values = vs;
        }
        return vs;
    }
複製程式碼

entrySet 方法

//獲取所有鍵值對的Map集合
public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
    	//entrySet = new LinkedEntrySet() 這裡是重點,LinkedEntrySet是LinkedHashMap的內部類
        //所以LinkedEntrySet可以獲取LinkedHashMap所有的屬性
        //而且在使用的時候才會獲取,不會在new的時候獲取
        return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
    }
複製程式碼

forEach 方法

public void forEach(BiConsumer<? super K, ? super V> action) {
    	//引數校驗
        if (action == null)
            throw new NullPointerException();
    	//獲取修改統計的副本
        int mc = modCount;
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
            //根據Entry的連結串列特性來遍歷
            //遍歷的節點呼叫BiConsumer的方法
            action.accept(e.key, e.value);
        if (modCount != mc)
            //如果遍歷中被其它執行緒修改了,丟擲異常
            throw new ConcurrentModificationException();
    }
複製程式碼

reinitialize 方法

//重新初始化,覆蓋父類的方法
void reinitialize() {
    	//呼叫父類的方法
        super.reinitialize();
    	//把頭節點和尾節點都置為null
        head = tail = null;
    }
複製程式碼

newNode 方法

//建立一個新的節點,覆蓋父類的方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    	//注意,這裡沒有呼叫父類的方法
    	//因為:LinkedHashMap的所有元素都是LinkedHashMap.Entry物件,是HashMap.Node的子類
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    	//把當前節點放到連結串列的尾部
        linkNodeLast(p);
    	//返回新建立的節點
        return p;
    }
複製程式碼

linkNodeLast 方法

//把傳入的節點新增到連結串列尾部
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;
        }
    }
複製程式碼

5. 未完待續...

  • 有很多方法在HashMap中實現,LinkedHashMap只重寫了方法中的一部分方法,這些需要結合HashMap一起看,建議在專案中用到的時候進行檢視,這部分方法後續在專案中用到會補充進來

相關文章