java集合框架(三):LinkedHashMap

        LinkedHashMap的數據結構跟HashMap相似,只是在節點上多加了指向前和向後的兩個屬性,也能夠歸納爲:數組+鏈表(雙向)+紅黑樹(java8增長了紅黑樹),以下圖所示:java

    

1、類的定義

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{}

2、存儲單元

        LinkedHashMap的基本存儲單元也是繼承HashMap的存儲單元,新增了兩個節點屬性,一個指向前,一個向後。node

// 定義在LinkedHashMap類中
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);
        }
}
// 定義在HashMap類中
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
}

3、構造函數

        構造函數主要是初始化父類的屬性值,LinkedHashMap新增的就只有一個:數組

boolean accessOrder = false;// 鏈表是否按照節點訪問順序排序,true:是,false:否。它起到什麼做用,我在文章後面會解析。數據結構

public LinkedHashMap() {
        // 調用父類構造器
        super();
        accessOrder = false;
}
// 指定初始容量
public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
}
// 指定初始容量和負載因子(負載因子通常使用默認的)
public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
}
// 指定初始容量,負載因子,是否按訪問順序排序
public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
}
// 能夠把其餘map的元素加載進來
public LinkedHashMap(Map<? extends K, ? extends V> m) {}

4、雙向鏈表的構造

        LinkedHashMap構建雙向鏈表是經過重寫父類HashMap的方法newNode(int hash, K key, V value, Node<K,V> e)實現的。當往LinkedHashMap中插入新節點的時候,其直接調用父類HashMap的put方法,put方法生產新節點的時候會調用newNode方法。函數

        來看看重寫以後的newNode方法作了什麼:測試

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

        看看雙向鏈表的實現,以下所示。其實很好理解,就是把新節點連接到尾節點後面,並把新節點從新賦值爲尾節點this

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        // 鏈表的尾節點
        LinkedHashMap.Entry<K,V> last = tail;
        // 新節點就是尾節點了
        tail = p;
        // 若是尾節點爲空,說明p是頭節點
        if (last == null)
            head = p;
        else {
            // 先後節點的連接
            p.before = last;
            last.after = p;
        }
}

5、遍歷實現

        LinkedHashMap的遍歷是經過LinkedHashIterator抽象類實現的,它從雙向鏈表的頭節點開始遍歷,直到鏈表的尾部。spa

abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        // 構造器初始化的時候把頭節點傳入
        LinkedHashIterator() {
            next = head;
            // fast-fail機制,遍歷時避免節點被改變
            expectedModCount = modCount;
            current = null;
        }
        // 迭代器的hasNext()方法會調用該方法
        public final boolean hasNext() {
            return next != null;
        }
        // 迭代器的next()方法會調用該方法
        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;
        }
}

        對比LinkedHashMap與HashMap的遍歷:code

public static void main(String[] args) {
        Map<String,String> linkedHashMap = new LinkedHashMap<String, String>();
        linkedHashMap.put("NO1","小明");
        linkedHashMap.put("NO2","小趙");
        linkedHashMap.put("NO3","小周");

        System.out.println("----遍歷LinkedHashMap----");
        for(Map.Entry entry:linkedHashMap.entrySet()){
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

        Map<String,String> hashMap = new HashMap<String, String>();
        hashMap.put("NO1","小明");
        hashMap.put("NO2","小趙");
        hashMap.put("NO3","小周");

        System.out.println("-----遍歷HashMap-------");
        for(Map.Entry entry:hashMap.entrySet()){
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
}

        測試結果:排序

        從上面的測試結果能夠看出,LinkedHashMap確實能夠對節點進行先進先出的排序。而HashMap的排序順序是不肯定的。

6、訪問順序排序

        文章開頭討論過屬性accessOrder,它決定鏈表是否按節點訪問的順序排序,若是爲true就是訪問順序排序,這時當某個存在的節點被操做過(好比被get()方法調用過,或者值被替換過),那麼先進先出的排序就會打亂,被訪問的節點會被排到最後。看一個例子:

public static void main(String[] args) {
        // 指定accessOrder爲true
        Map<String,String> linkedHashMap = new LinkedHashMap<String, String>(10,0.75f,true);
        linkedHashMap.put("NO1","小明");
        linkedHashMap.put("NO2","小趙");
        linkedHashMap.put("NO3","小周");
        // 對NO1進行get操做
        linkedHashMap.get("NO1");

        System.out.println("----按訪問順序遍歷LinkedHashMap----");
        for(Map.Entry entry:linkedHashMap.entrySet()){
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
}

        測試結果:

        訪問排序的實現是在每一個操做函數裏面調用了方法afterNodeAccess,來看看它的實現:

void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            // 如下操做主要把當前訪問的節點移到鏈表的尾部
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
}

7、總結

        LinkedHashMap在HashMap的基礎上多維護了一條雙向鏈表,其最重要特徵就是能夠對map進行先進先出的遍歷操做,若有須要也能夠對其按訪問順序遍歷。

        以上就是我對LinkedHashMap的原理的理解,若有錯誤之處,請批評和指正。

相關文章
相關標籤/搜索