java.util.LinkedHashMap雙向鏈表有序源碼(jdk1.7)

準備工作

由于LinkedHashMap也是繼承HashMap,在HashMap類的基礎上進行的功能擴展,所以先了解下HashMap :http://www.lxweimin.com/p/374546518bb6

LinkedHashMap中鏈地址的雙向循環鏈表結構

        // 雙向循環鏈表維護沖突值 
        private static class Entry<K,V> extends HashMap.Entry<K,V> {  
            // 雙向循環鏈表的前驅節點和后繼結點  
            Entry<K,V> before, after;  
      
            Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {  
                super(hash, key, value, next);  
        }  
        // 通過前驅后繼關系將節點刪除    
        private void remove() {  
            // 當前節點的前驅節點的后繼為當前節點的后繼結點  
            before.after = after;  
            // 當前節點的后繼結點的前驅為當前節點的前驅節點  
            after.before = before;  
        }  

說明:使用前驅節點和后繼節點刪除當前節點

        /**  
         * 將existingEntry指定節點前面插入當前節點。  
         * existingEntry :現有項  
         */  
        private void addBefore(Entry<K,V> existingEntry) {  
            //this.after =existingEntry;  
            after  = existingEntry;  
            //this.before =existingEntry.before;  
            before = existingEntry.before;  
            before.after = this;  
            after.before = this;  
        }  

說明(重要設計):existingEntry指定節點作為當前節點的后繼節點,existingEntry指定節點的前驅節點作為當前節點的前驅節點,對于before和after本身就是當前節點的前驅節點和后繼節點,再修改下,讓前驅節點的后繼指針指向當前節點,讓后繼節點的前驅指針指向當前節點即可。完成了將existingEntry指定節點前面插入當前節點。

        /**  
         *  如果LinkedHashMap的排序順序為訪問順序,那么就將當前節點插入到頭結點前面  
         */  
        void recordAccess(HashMap<K,V> m) {  
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
            //如果排序順序為訪問順序  
            if (lm.accessOrder) {  
                // LinkedHashMap修改次數加1
                lm.modCount++;  
                // 刪除當前節點  
                remove();  
                // 在頭節點前面插入當前節點  
                addBefore(lm.header);  
            }  
        }  

說明:如果排序順序為訪問順序,那么將在頭結點前面插入當前節點。

        void recordRemoval(HashMap<K,V> m) {  
            remove();  
        }  
    }  

屬性

    // 雙向鏈表的頭結點    
    private transient Entry<K,V> header;  

說明:雙向鏈表的頭節點。

    // 排序模式:對于訪問順序,為 true;對于插入順序,則為 false。   
    private final boolean accessOrder; 

說明:節點的排序方式:對于訪問順序,為 true;對于插入順序,則為 false。

構造方法

構造方法1:傳入初始化容量,加載因子,默認采用插入順序排序的HashMap構造

    public LinkedHashMap(int initialCapacity, float loadFactor) {  
        super(initialCapacity, loadFactor);  
        accessOrder = false;  
    }  

構造方法2:傳入初始化容量,默認加載因子為0.75,默認采用插入順序排序的HashMap構造

    public LinkedHashMap(int initialCapacity) {  
        super(initialCapacity);  
        accessOrder = false;  
    }  

構造方法3:采用默認初始化容量16,默認加載因子為0.75,默認插入順序排序的HashMap構造

    public LinkedHashMap() {  
        super();  
        accessOrder = false;  
    }  

構造方法4:構造一個映射關系與指定 Map 相同的 HashMap; 所創建的 HashMap 具有默認的加載因子 (0.75) 和足以容納指定 Map 中映射關系的初始容量; 采用默認插入順序排序

    public LinkedHashMap(Map<? extends K, ? extends V> m) {  
        super(m);  
        accessOrder = false;  
    }  

構造方法5:構造一個擁有初始化容量、加載因子、排序順序的LinkedHashMap

    public LinkedHashMap(int initialCapacity,  
                         float loadFactor,  
                         boolean accessOrder) {  
        super(initialCapacity, loadFactor);  
        this.accessOrder = accessOrder;  
    }  

方法

init方法,初始化雙向循環鏈表

   @Override  
    void init() {  
        header = new Entry<>(-1, null, null, null);  
        header.before = header.after = header;  
    }  

createEntry方法:創建節點,將頭結點插入到當前節點的前面。

void createEntry(int hash, K key, V value, int bucketIndex) { 
    // 通過下標獲取鍵表中對應的節點Entry
    HashMap.Entry<K,V> old = table[bucketIndex];
    // 創建一個節點,后繼節點指向old
    Entry<K,V> e = new Entry<>(hash, key, value, old);  
    // 插入節點e
    table[bucketIndex] = e;  
    // e節點設置為頭結點的后繼節點
    e.addBefore(header);
    size++;  
}

說明:

  1. 雖然HashMap查找元素的方式是,通過鍵找到hash值,通過hash值找到下標,通過下標找到節點鏈。
  2. LinkedHashMap在原來的HashMap基礎上,將單向節點鏈中的每個節點又額外構造了一條雙向鏈表,插入值的順序,就是在頭結點的后面插入新節點(類似于不停的插隊的節奏)。

transfer方法:將所有元素都放到新的數組中,重寫父類的HashMap
的transfer方法。

        /**  
         * 將所有的元素放置到新的數組中  
         * 重寫父類HashMap的transfer方法  
         */  
        @Override  
        void transfer(HashMap.Entry[] newTable, boolean rehash) {  
            int newCapacity = newTable.length;  
            // 使用雙向鏈表的頭結點進行循環遍歷  
            for (Entry<K,V> e = header.after; e != header; e = e.after) {  
                if (rehash)  
                    e.hash = (e.key == null) ? 0 : hash(e.key);  
                // 通過hash值獲取對應的下標索引  
                int index = indexFor(e.hash, newCapacity);  
                // 插入到鏈表表頭  
                e.next = newTable[index];  
                // 新數組的索引對應的值為header  
                newTable[index] = e;  
            }  
        }  

說明:遍歷雙向鏈表,從頭結點的后繼節點開始遍歷,然后將遍歷到的節點設置到新的鍵表中。


get方法:返回此映射中映射到指定鍵的值。

    public V get(Object key) {  
            // 調用父類的getEntry方法,通過鍵key來獲取對應的Entry  
            Entry<K,V> e = (Entry<K,V>)getEntry(key);  
             // 如果e為null,那么根本不存在對應的Entry就更沒有對應的值了,所以返回null  
            if (e == null)  
                return null;  
            // 將當前節點插入到頭結點前面  
            e.recordAccess(this);  
            return e.value;  
        }  

說明:通過鍵獲取節點,通過節點獲取值。


迭代:

private class KeyIterator extends LinkedHashIterator<K> {
     public K next() { return nextEntry().getKey(); }
}

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;
 }

總結:

  1. LinkedHashMap存儲沖突值使用雙向循環鏈表。
  2. LinkedHashMap中鍵和值都允許存入null值。
  3. LinkedHashMap中值允許重復,如果發現鍵相同,就更新原來的值。
  4. LinkedHashMap是線程不安全的。
  5. LinkedHashMap中accessOrder屬性,true意味著排序模式為訪問順序遍歷出來,false意味著排序模式為插入順序遍歷出來。
  6. LinkedHashMap單獨維護了一條雙向鏈表,保證了按照插入的順序設計的,所以取得時候就會保證有序。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容