LinkedHashMap就這麼簡單【原始碼剖析】

Java3y發表於2019-03-04

前言

宣告,本文用得是jdk1.8

前面已經講了Collection的總覽和剖析List集合以及雜湊表、Map集合、紅黑樹還有HashMap基礎了:

本篇主要講解LinkedHashMap~

看這篇文章之前最好是有點資料結構的基礎:

當然了,如果講得有錯的地方還請大家多多包涵並不吝在評論去指正~

一、LinkedHashMap剖析

LinkedHashMap資料結構圖:

LinkedHashMap就這麼簡單【原始碼剖析】

ps:圖片來源網路,侵刪~

首先我們來看看類繼承圖:

LinkedHashMap就這麼簡單【原始碼剖析】

我簡單翻譯了一下頂部的註釋(我英文水平渣,如果有錯的地方請多多包涵~歡迎在評論區下指正)

LinkedHashMap就這麼簡單【原始碼剖析】

從頂部翻譯我們就可以歸納總結出HashMap幾點:

  • 底層是雜湊表和雙向連結串列
  • 允許為null,不同步
  • 插入的順序是有序的(底層連結串列致使有序)
  • 裝載因子和初始容量對LinkedHashMap影響是很大的~

同時也給我帶了幾個疑問:

  • access-ordered和insertion-ordered具體的使用和意思
  • 為什麼說初始容量對遍歷沒有影響?

希望可以在看原始碼的過程中可以解決掉我這兩個疑問~那接下來就開始吧~

1.1LinkedHashMap的域

LinkedHashMap就這麼簡單【原始碼剖析】

1.2LinkedHashMap重寫的方法

下面我列舉就這兩個比較重要的:

LinkedHashMap就這麼簡單【原始碼剖析】

這就印證了我們的LinkedHashMap底層確確實實是雜湊表和雙向連結串列~

  • 在構建新節點時,構建的是LinkedHashMap.Entry 不再是Node.

1.3構造方法

可以發現,LinkedHashMap有5個構造方法

LinkedHashMap就這麼簡單【原始碼剖析】

下面我們來看看構造方法的定義是怎麼樣的:

LinkedHashMap就這麼簡單【原始碼剖析】

從構造方法上我們可以知道的是:LinkedHashMap預設使用的是插入順序

1.4put方法

原本我是想要找put方法,看看是怎麼實現的,後來沒找著,就奇了個怪~

LinkedHashMap就這麼簡單【原始碼剖析】

再頓了一下,原來LinkedHashMap和HashMap的put方法是一樣的!LinkedHashMap繼承著HashMap,LinkedHashMap沒有重寫HashMap的put方法

所以,LinkedHashMap的put方法和HashMap是一樣的。

如果沒看過HashMap就是這麼簡單【原始碼剖析】的同學,可進去看看~

當然了,在建立節點的時候,呼叫的是LinkedHashMap重寫的方法~

LinkedHashMap就這麼簡單【原始碼剖析】

1.5get方法

LinkedHashMap就這麼簡單【原始碼剖析】

LinkedHashMap就這麼簡單【原始碼剖析】

get方法也是多了:判斷是否為訪問順序~~~

講到了這裡,感覺我們可以簡單測試一波了:

首先我們來看看已插入順序來進行插入和遍歷:


    public static void insertOrder() {

        // 預設是插入順序
        LinkedHashMap<Integer,String>  insertOrder = new LinkedHashMap();

        String value = "關注公眾號Java3y";
        int i = 0;

        insertOrder.put(i++, value);
        insertOrder.put(i++, value);
        insertOrder.put(i++, value);
        insertOrder.put(i++, value);
        insertOrder.put(i++, value);

        //遍歷
        Set<Integer> set = insertOrder.keySet();
        for (Integer s : set) {
            String mapValue = insertOrder.get(s);
            System.out.println(s + "---" + mapValue);
        }
    }
複製程式碼

測試一波:

LinkedHashMap就這麼簡單【原始碼剖析】

接著,我們來測試一下以訪問順序來進行插入和遍歷:

    public static void accessOrder() {
        // 設定為訪問順序的方式
        LinkedHashMap<Integer,String> accessOrder = new LinkedHashMap(16, 0.75f, true);

        String value = "關注公眾號Java3y";
        int i = 0;
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);


        // 遍歷
        Set<Integer> sets = accessOrder.keySet();
        for (Integer key : sets) {
            String mapValue = accessOrder.get(key);
            System.out.println(key + "---" + mapValue);
        }

    }

複製程式碼

程式碼看似是沒有問題,但是執行會出錯的!

LinkedHashMap就這麼簡單【原始碼剖析】

前面在看原始碼註釋的時候我們就發現了:在AccessOrder的情況下,使用get方法也是結構性的修改

為了簡單看出他倆的區別,下面我就直接用key來進行看了~

以下是訪問順序的測試



    public static void accessOrder() {
        // 設定為訪問順序的方式
        LinkedHashMap<Integer,String> accessOrder = new LinkedHashMap(16, 0.75f, true);

        String value = "關注公眾號Java3y";
        int i = 0;
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);
        accessOrder.put(i++, value);


        // 訪問一下key為3的元素再進行遍歷
        accessOrder.get(3);

        // 遍歷
        Set<Integer> sets = accessOrder.keySet();
        for (Integer key : sets) {

            System.out.println(key );
        }

    }

複製程式碼

測試結果:

LinkedHashMap就這麼簡單【原始碼剖析】

以下是插入順序的測試(程式碼就不貼了,和上面幾乎一樣):

LinkedHashMap就這麼簡單【原始碼剖析】

我們可以這樣理解:最常用的將其放在連結串列的最後,不常用的放在連結串列的最前~

這個知識點以我的理解而言,它這個訪問順序在LinkedHashMap如果不重寫用處並不大~它是用來給別的實現進行擴充套件

  • 因為最常被使用的元素再遍歷的時候卻放在了最後邊,在LinkedHashMap中我也沒找到對應的方法來進行呼叫~
  • 一個removeEldestEntry(Map.Entry<K,V> eldest)方法,重寫它可以刪除最久未被使用的元素!!
  • 還有一個是afterNodeInsertion(boolean evict)方法,新增時判斷是否需要刪除最久未被使用的元素!!

LinkedHashMap就這麼簡單【原始碼剖析】

去網上搜了幾篇資料,都是講LRUMap的實現的(也就是對LinkedHashMap進行擴充套件),有興趣的同學可參考下列連結:

1.6remove方法

對於remove方法,在LinkedHashMap中也沒有重寫,它呼叫的還是父類的HashMap的remove()方法,在LinkedHashMap中重寫的是:afterNodeRemoval(Node<K,V> e)這個方法

LinkedHashMap就這麼簡單【原始碼剖析】

當然了,在remove的時候會涉及到上面重寫的方法:

LinkedHashMap就這麼簡單【原始碼剖析】

1.7遍歷的方法

Set<Map.Entry<K,V>> entrySet()是被重寫的了

LinkedHashMap就這麼簡單【原始碼剖析】

LinkedHashMap就這麼簡單【原始碼剖析】

看到了這裡,我們就知道為啥註釋說:初始容量對遍歷沒有影響

因為它遍歷的是LinkedHashMap內部維護的一個雙向連結串列,而不是雜湊表(當然了,連結串列雙向連結串列的元素都來源於雜湊表)

二、總結

LinkedHashMap比HashMap多了一個雙向連結串列的維護,在資料結構而言它要複雜一些,閱讀原始碼起來比較輕鬆一些,因為大多都由HashMap實現了..

閱讀原始碼的時候我們會發現多型是無處不在的~子類用父類的方法,子類重寫了父類的部分方法即可達到不一樣的效果!

  • 比如:LinkedHashMap並沒有重寫put方法,而put方法內部的newNode()方法重寫了。LinkedHashMap呼叫父類的put方法,裡面回撥的是重寫後的newNode(),從而達到目的!

LinkedHashMap可以設定兩種遍歷順序:

  • 訪問順序(access-ordered)
  • 插入順序(insertion-ordered)
  • 預設是插入順序的

對於訪問順序,它是LRU(最近最少使用)演算法的實現,要使用它要麼重寫LinkedListMap的幾個方法(removeEldestEntry(Map.Entry<K,V> eldest)afterNodeInsertion(boolean evict)),要麼是擴充套件成LRUMap來使用,不然設定為訪問順序(access-ordered)的用處不大~

LinkedHashMap遍歷的是內部維護的雙向連結串列,所以說初始容量對LinkedHashMap遍歷是不受影響的

參考資料:


明天要是無意外的話,可能會寫TreeMap,敬請期待哦~~~~

LinkedHashMap就這麼簡單【原始碼剖析】

文章的目錄導航zhongfucheng.bitcron.com/post/shou-j…

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y。為了大家方便,剛新建了一下qq群:742919422,大家也可以去交流交流。謝謝支援了!希望能多介紹給其他有需要的朋友

相關文章