1. 程式人生 > >【深入學習java集合系列】LinkedHashMap的底層實現

【深入學習java集合系列】LinkedHashMap的底層實現

最近寫到LeetCode上的某一題LRUCache。可以採用LinkedHashMap實現,通過重寫removeEldestEntry方法,即可實現。

    LinkedHashMap map;

    public LRUCache(int capacity) {
        map = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
            // 定義put後的移除規則,大於容量就刪除eldest
            protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
                return size() > capacity;
            }
        };
    }

    public int get(int key) {
        return map.containsKey(key) ? (int)map.get(key) : -1 ;
    }

    public void put(int key, int value) {
            map.put(key, value);
    }

但是,這樣實在太耍賴了,所以決定研究LinkedHashMap的底層到底是如何實現的?原始碼應該是最好的學習程式碼了。

1、繼承關係

  1. publicclass LinkedHashMap<K,V>  
  2.     extends HashMap<K,V>  
  3.     implements Map<K,V>  

linkedhashmap繼承hashMap,底層的主要儲存結構是Hashmap的table。LinkedHashMap實現與HashMap的不同之處在於,後者維護著一個運行於所有條目的雙重連結列表。此連結列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。

2、成員變數

  1. privatetransient Entry<K,V> header;
  2. privatefinalboolean accessOrder;  

首先header是一個雙向連結串列,具體體現在Entry結構中,我們知道hashmap的Entry是一個單向連結串列,只有一個next屬性。

其次就是accessOrder,它決定了linkedhashmap中的元素在遍歷的時候的輸出順序(插入順序或者是訪問順序)。

3、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); } }

4、建構函式

  1. //預設accessOrder為false
  2. //呼叫HashMap建構函式
  3. public LinkedHashMap() {  
  4.     super();  
  5.     accessOrder = false;  
  6. }  
  7. //如果想實現LRU演算法,參考這個建構函式
  8. public LinkedHashMap(int initialCapacity, float loadFactor,  
  9.         boolean accessOrder) {  
  10.     super(initialCapacity, loadFactor);  
  11.     this.accessOrder = accessOrder;  
  12. }  
  13. //模板方法模式,HashMap建構函式裡面的會呼叫init()方法
  14. //初始化的時候map裡沒有任何Entry,讓header.before = header.after = header
  15. void init() {  
  16.     header = new Entry(-1nullnullnull);  
  17.     header.before = header.after = header;  
  18. }

5、儲存資料

LinkedHashMap並未重寫父類HashMap的put方法,而是重寫了父類HashMap的put方法呼叫的子方法void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的雙向連結列表的實現。

  1. //LinkedHashMap沒有put(K key, V value)方法,只重寫了被put呼叫的addEntry方法
  2. //1是HashMap裡原有的邏輯,23是LinkedHashMap特有的
  3. void addEntry(int hash, K key, V value, int bucketIndex) {  
  4.     createEntry(hash, key, value, bucketIndex);  
  5.     Entry eldest = header.after;  
  6.     //3.如果有必要,移除LRU裡面最老的Entry,否則判斷是否該resize
  7.     if (removeEldestEntry(eldest)) {  
  8.         removeEntryForKey(eldest.key);  
  9.     } else {  
  10.         if (size >= threshold)  
  11.             resize(2 * table.length);  
  12.     }  
  13. }  
  14. void createEntry(int hash, K key, V value, int bucketIndex) {  
  15.     //1.同HashMap一樣:在Entry陣列+next連結串列結構裡面加入Entry
  16.     HashMap.Entry old = table[bucketIndex];  
  17.     Entry e = new Entry(hash, key, value, old);  
  18.     table[bucketIndex] = e;  
  19.     //2.把新Entry也加到header連結串列結構裡面去
  20.     e.addBefore(header);  
  21.     size++;  
  22. }  
  23. //預設是false,我們可以重寫此方法
  24. protectedboolean removeEldestEntry(Map.Entry eldest) {  
  25.     returnfalse;  
  26. }  
  27. privatestaticclass Entry extends HashMap.Entry {  
  28.     //連結串列插入元素四個步驟,對著圖看
  29.     privatevoid addBefore(Entry existingEntry) {  
  30.         after = existingEntry;                //1
  31.         before = existingEntry.before;     //2
  32.         before.after = this;                   //3
  33.         after.before = this;                   //4
  34.     }  
  35.        }  
  36.         //如果走到resize,會呼叫這裡重寫的transfer
  37. //HashMap裡面的transfer是n * m次運算,LinkedHashtable重寫後是n + m次運算
  38. void transfer(HashMap.Entry[] newTable) {  
  39.     int newCapacity = newTable.length;  
  40.     //直接遍歷header連結串列,HashMap裡面是遍歷Entry陣列
  41.     for (Entry e = header.after; e != header; e = e.after) {  
  42.         int index = indexFor(e.hash, newCapacity);  
  43.         e.next = newTable[index];  
  44.         newTable[index] = e;  
  45.     }  
  46.  }  
6、讀取資料

LinkedHashMap重寫了父類HashMap的get方法,實際在呼叫父類getEntry()方法取得查詢的元素後,再判斷當排序模式accessOrder為true時,記錄訪問順序,將最新訪問的元素新增到雙向連結串列的表頭,並從原來的位置刪除。由於的連結串列的增加、刪除操作是常量級的,故並不會帶來效能的損失。

  1. //重寫了get(Object key)方法
  2. public V get(Object key) {  
  3.     //1.呼叫HashMap的getEntry方法得到e
  4.     Entry e = (Entry) getEntry(key);  
  5.     if (e == null)  
  6.         returnnull;  
  7.     //2.LinkedHashMap牛B的地方
  8.     e.recordAccess(this);  
  9.     return e.value;  
  10. }  
  11.        // 繼承了HashMap.Entry
  12. privatestaticclass Entry extends HashMap.Entry {  
  13.     //1.此方法提供了LRU的實現
  14.     //2.通過12兩步,把最近使用的當前Entry移到header的before位置,而LinkedHashIterator遍歷的方式是從header.after開始遍歷,先得到最近使用的Entry
  15.     //3.最近使用是什麼意思:accessOrder為true時,get(Object key)方法會導致Entry最近使用;put(K key, V value)/putForNullKey(value)只有是覆蓋操作時會導致Entry最近使用。它們都會觸發recordAccess方法從而導致Entry最近使用
  16.     //4.總結LinkedHashMap迭代方式:accessOrder=false時,迭代出的資料按插入順序;accessOrder=true時,迭代出的資料按LRU順序+插入順序
  17.     //  HashMap迭代方式:橫向陣列 * 豎向next連結串列
  18.     void recordAccess(HashMap m) {  
  19.         LinkedHashMap lm = (LinkedHashMap) m;  
  20.         //如果使用LRU演算法
  21.         if (lm.accessOrder) {  
  22.             lm.modCount++;  
  23.             //1.從header連結串列裡面移除當前Entry
  24.             remove();  
  25.             //2.把當前Entry移到header的before位置
  26.             addBefore(lm.header);  
  27.         }  
  28.     }  
  29.     //讓當前Entry從header連結串列消失
  30.     privatevoid remove() {  
  31.         before.after = after;  
  32.         after.before = before;  
  33.     }  
  34.        }  

7、刪除資料

  1. // 繼承了HashMap.Entry
  2. privatestaticclass Entry extends HashMap.Entry {  
  3.     //LinkedHashMap沒有重寫remove(Object key)方法,重寫了被remove呼叫的recordRemoval方法
  4.     //這個方法的設計也和精髓,也是模板方法模式
  5.     //HahsMap remove(Object key)把資料從橫向陣列 * 豎向next連結串列裡面移除之後(就已經完成工作了,所以HashMap裡面recordRemoval是空的實現呼叫了此方法
  6.     //但在LinkedHashMap裡面,還需要移除header連結串列裡面Entry的after和before關係
  7.     void recordRemoval(HashMap m) {  
  8.         remove();  
  9.     }  
  10.     //讓當前Entry從header連結串列消失
  11.     privatevoid remove() {  
  12.         before.after = after;  
  13.         after.before = before;  
  14.     }  
  15. }  
8、遍歷資料
  1. privateabstractclass LinkedHashIterator implements Iterator {  
  2.     //從header.after開始遍歷
  3.     Entry nextEntry = header.after;  
  4.     Entry nextEntry() {  
  5.         if (modCount != expectedModCount)  
  6.             thrownew ConcurrentModificationException();  
  7.         if (nextEntry == header)  
  8.             thrownew NoSuchElementException();  
  9.         Entry e = lastReturned = nextEntry;  
  10.         nextEntry = e.after;  
  11.         return e;  
  12.     }  
  13. }  

九.總結

  1. LinkedHashMap繼承HashMap,結構2裡資料結構的變化交給HashMap就行了。
  2. 結構1裡資料結構的變化就由LinkedHashMap裡重寫的方法去實現。
  3. 簡言之:LinkedHashMap比HashMap多維護了一個連結串列。

相關推薦

深入學習java集合系列LinkedHashMap底層實現

最近寫到LeetCode上的某一題LRUCache。可以採用LinkedHashMap實現,通過重寫removeEldestEntry方法,即可實現。 LinkedHashMap map; public LRUCache(int capacity) {

深入理解Java集合框架紅黑樹講解(上)

時間復雜度 row lee tel framework 關系 eight logs return 來源:史上最清晰的紅黑樹講解(上) - CarpenterLee 作者:CarpenterLee(轉載已獲得作者許可,如需轉載請與原作者聯系) 文中所有圖片點擊之後均可查看大

深入理解Java 虛擬機器學習筆記一

目錄 執行時資料區域 根索引演算法 垃圾回收演算法 垃圾收集器 雙親委派模型 JDK命令列工具Jstack 和 JConsole 1、執行時資料區域 執行緒共享:方法區、堆 執行緒私有:虛擬機器棧、本地方法棧、程式計數器 2、根索引演

Java集合系列 總體框架

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

深入理解java集合-ArryList實現原理

一、ArrayList簡介 1、概述 ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。 ArrayList不是執行緒安全的,只能用在單執行緒環境下,多執行緒環境下可以考慮用Collections.

深入理解java集合-LinkedList實現原理

一、LinkeddList簡介 1、LinkedList概述 LinkedList是一個一個繼承於AbstractSequentialList,並實現了List介面和Deque介面的雙端連結串列。 LinkedList底層的連結串列結構使它支援高效的插入和刪除操作,

深入理解java集合-TreeMap實現原理

一、紅黑樹介紹 1、R-B Tree概念 紅黑樹(Red Black Tree,簡稱R-B Tree) 是一種自平衡二叉查詢樹,它雖然是複雜的,但它的最壞情況執行時間也是非常良好的,並且在實踐中是高效的: 它可以在O(log n)時間內做查詢,插入和刪除,這裡的n 是

Java集合系列 總體框架

根據上面的類圖,我們可以把java的所有集合分成三大類,其中Set集合類類似於一個糖罐子,把一個物件新增到Set集合裡面的時候,Set集合無法記住新增這個元素的順序,所以Set裡面的元素不能重複,否則系統無法準確識別這個元素;List集合非常像一個數組,她可以記住每次新增元素的順序,可以重複,只是List的長

深入理解Java虛擬機器學習小結

第一章 走近Java 摘書 Java各個版本新特性 1.0:Java1.0提供了一個純解釋執行的Java虛擬機器實現(Sun Classic VM)。JDK1.0版本的代表技術包括:Java虛擬機

Java集合系列---總體框架

集合--童年的美好時光集合,忽然讓小編想起那段美好的學生時光,集合第一次遇見她的時候,小編當年還是一個懵懂的丫頭,也不曾想過會在計算機的世界再次相遇,再回首,集合在數學中是一個基本概念,集合就是“一堆東

Java集合 LinkedHashMap(有序的map)獲取第一個元素和最後一個元素

獲取LinkedHashMap中的頭部元素(最早新增的元素): 時間複雜度O(1) public <K, V> Entry<K, V> getHead(LinkedHashMap<K, V> map) { retu

java集合系列---HashSet

在前面的博文中,小編主要簡單介紹了java集合中的總體框架,以及list介面中典型的集合ArrayList和LinkedList,接著,我們來看set的部分集合,set集合和數學意義上的集合沒有差別,作為集合,可以容納多個元素,而且,集合裡面沒有重複的元素,Set集合是Col

深入理解JAVA集合系列四:ArrayList源碼解讀

結束 了解 數組下標 size new 數組元素 開始 ini rem 在開始本章內容之前,這裏先簡單介紹下List的相關內容。 List的簡單介紹 有序的collection,用戶可以對列表中每個元素的插入位置進行精確的控制。用戶可以根據元素的整數索引(在列表中的位置)訪

深入理解JAVA集合系列三:HashMap的死循環解讀

現在 最新 star and 場景 所有 image cap 時也 由於在公司項目中偶爾會遇到HashMap死循環造成CPU100%,重啟後問題消失,隔一段時間又會反復出現。今天在這裏來仔細剖析下多線程情況下HashMap所帶來的問題: 1、多線程put操作後,get操作導

由淺入深理解java集合(二)——集合 Set

找到 str rip ges 地址 view 包括 細節 無法 上一篇文章介紹了Set集合的通用知識。Set集合中包含了三個比較重要的實現類:HashSet、TreeSet和EnumSet。本篇文章將重點介紹這三個類。    一、HashSet類 HashSet簡介

深入理解java虛擬機器第0集--Java記憶體區域和java記憶體模型

首先我們清楚【記憶體區域】和【記憶體模型】是兩個不一樣的概念。當時我電面阿里的時候,面試官讓我講講記憶體模型的理解,我巴拉巴拉說了一通方法區-堆分割槽,垃圾演算法,面試官耐心的聽我說完就把電話掛了。 【記憶體區域】對應的是jvm程序。jvm啟動之後,自身是一個大的程序,作業

深入學習java集合JAVA集合類主要介面

       Iterator介面主要用於遍歷 Collection 集合中的元素,Iterator物件也被稱為迭代器。Iterator介面隱藏了各種 Collection 實現類的底層細節,嚮應用程式提供了遍歷 Collection 集合元素的統一程式設計介面。Iterator僅用於遍歷集合,Iter

深入理解Java虛擬機器Java記憶體區域模型、物件建立過程、常見OOM

本文內容來源於《深入理解Java虛擬機器》一書,非常推薦大家去看一下這本書。最近開始看這本書,打算再開一個相關係列,來總結一下這本書中的重要知識點。呃呃呃,說好的那個圖片請求框架呢~  不要急哈,因為這個請求框架設計的內容還是比較廣的,目前業餘時間正在編寫當中,弄好了之後就會

深入理解Java虛擬機器垃圾回收機制

本文內容來源於《深入理解Java虛擬機器》一書,非常推薦大家去看一下這本書。本系列其他文章:1、垃圾回收要解決的問題垃圾收集(Garbage Collection,GC),要設計一個GC,需要考慮解決下面三件事情:(1)哪些記憶體需要回收?(2)什麼時候回收?(3)如何回收?

深入理解Java虛擬機器類載入機制

本文內容來源於《深入理解Java虛擬機器》一書,非常推薦大家去看一下這本書。本系列其他文章:【深入理解Java虛擬機器】垃圾回收機制1、類載入機制概述虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Jav