Java集合系列之LinkedHashMap源碼分析,linkedhashmap源碼

來源:互聯網
上載者:User

Java集合系列之LinkedHashMap源碼分析,linkedhashmap源碼

這篇文章我們開始分析LinkedHashMap的源碼,LinkedHashMap繼承了HashMap,也就是說LinkedHashMap是在HashMap的基礎上擴充而來的,因此在看LinkedHashMap源碼之前,讀者有必要先去瞭解HashMap的源碼,可以查看我上一篇文章的介紹《Java集合系列[3]----HashMap源碼分析》。只要深入理解了HashMap的實現原理,回過頭來再去看LinkedHashMap,HashSet和LinkedHashSet的源碼那都是非常簡單的。因此,讀者們好好耐下性子來研究研究HashMap源碼吧,這可是買一送三的好生意啊。在前面分析HashMap源碼時,我採用以問題為導向對源碼進行分析,這樣使自己不會像無頭蒼蠅一樣亂分析一通,讀者也能夠針對問題更加深入的理解。本篇我決定還是採用這樣的方式對LinkedHashMap進行分析。

1. LinkedHashMap內部採用了什麼樣的結構?

可以看到,由於LinkedHashMap是繼承自HashMap的,所以LinkedHashMap內部也還是一個雜湊表,只不過LinkedHashMap重新寫了一個Entry,在原來HashMap的Entry上添加了兩個成員變數,分別是前繼結點引用和後繼結點引用。這樣就將所有的結點連結在了一起,構成了一個雙向鏈表,在擷取元素的時候就直接遍曆這個雙向鏈表就行了。我們看看LinkedHashMap實現的Entry是什麼樣子的。

private static class Entry<K,V> extends HashMap.Entry<K,V> {  //當前結點在雙向鏈表中的前繼結點的引用  Entry<K,V> before;  //當前結點在雙向鏈表中的後繼結點的引用  Entry<K,V> 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;  }  //將當前結點插入到雙向鏈表中一個已存在的結點前面  private void addBefore(Entry<K,V> existingEntry) {    //當前結點的下一個結點的引用指向給定結點    after = existingEntry;    //當前結點的上一個結點的引用指向給定結點的上一個結點    before = existingEntry.before;    //給定結點的上一個結點的下一個結點的引用指向當前結點    before.after = this;    //給定結點的上一個結點的引用指向當前結點    after.before = this;  }  //按訪問順序排序時, 記錄每次擷取的操作  void recordAccess(HashMap<K,V> m) {    LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;    //如果是按訪問順序排序    if (lm.accessOrder) {      lm.modCount++;      //先將自己從雙向鏈表中移除      remove();      //將自己放到雙向鏈表尾部      addBefore(lm.header);    }  }    void recordRemoval(HashMap<K,V> m) {    remove();  }}

2. LinkedHashMap是怎樣實現按插入順序排序的?

//父類put方法中會調用的該方法void addEntry(int hash, K key, V value, int bucketIndex) {  //調用父類的addEntry方法  super.addEntry(hash, key, value, bucketIndex);  //下面操作是方便LRU緩衝的實現, 如果緩衝容量不足, 就移除最老的元素  Entry<K,V> eldest = header.after;  if (removeEldestEntry(eldest)) {    removeEntryForKey(eldest.key);  }}//父類的addEntry方法中會調用該方法void createEntry(int hash, K key, V value, int bucketIndex) {  //先擷取HashMap的Entry  HashMap.Entry<K,V> old = table[bucketIndex];  //封裝成LinkedHashMap自身的Entry  Entry<K,V> e = new Entry<>(hash, key, value, old);  table[bucketIndex] = e;  //將當前結點插入到雙向鏈表的尾部  e.addBefore(header);  size++;}

LinkedHashMap重寫了它的父類HashMap的addEntry和createEntry方法。當要插入一個索引值對的時候,首先會調用它的父類HashMap的put方法。在put方法中會去檢查一下雜湊表中是不是存在了對應的key,如果存在了就直接替換它的value就行了,如果不存在就調用addEntry方法去建立一個Entry。注意,這時候就調用到了LinkedHashMap自己的addEntry方法。我們看到上面的代碼,這個addEntry方法除了回調父類的addEntry方法之外還會調用removeEldestEntry去移除最老的元素,這步操作主要是為了實現LRU演算法,下面會講到。我們看到LinkedHashMap還重寫了createEntry方法,當要建立一個Entry的時候最終會調用這個方法,createEntry方法在每次將Entry放入到雜湊表之後,就會調用addBefore方法將當前結點插入到雙向鏈表的尾部。這樣雙向鏈表就記錄了每次插入的結點的順序,擷取元素的時候只要遍曆這個雙向鏈表就行了,示範了每次調用addBefore的操作。由於是雙向鏈表,所以將當前結點插入到頭結點之前其實就是將當前結點插入到雙向鏈表的尾部。

3. 怎樣利用LinkedHashMap實現LRU緩衝?

我們知道緩衝的實現依賴於電腦的記憶體,而記憶體資源是相當有限的,不可能無限制的存放元素,所以我們需要在容量不夠的時候適當的刪除一些元素,那麼到底刪除哪個元素好呢?LRU演算法的思想是,如果一個資料最近被訪問過,那麼將來被訪問的幾率也更高。所以我們可以刪除那些不經常被訪問的資料。接下來我們看看LinkedHashMap內部是怎樣實現LRU機制的。

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {  //雙向鏈表頭結點  private transient Entry<K,V> header;  //是否按訪問順序排序  private final boolean accessOrder;  ...  public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {    super(initialCapacity, loadFactor);    this.accessOrder = accessOrder;  }  //根據key擷取value值  public V get(Object key) {    //調用父類方法擷取key對應的Entry    Entry<K,V> e = (Entry<K,V>)getEntry(key);    if (e == null) {      return null;    }    //如果是按訪問順序排序的話, 會將每次使用後的結點放到雙向鏈表的尾部    e.recordAccess(this);    return e.value;  }  private static class Entry<K,V> extends HashMap.Entry<K,V> {    ...    //將當前結點插入到雙向鏈表中一個已存在的結點前面    private void addBefore(Entry<K,V> existingEntry) {      //當前結點的下一個結點的引用指向給定結點      after = existingEntry;      //當前結點的上一個結點的引用指向給定結點的上一個結點      before = existingEntry.before;      //給定結點的上一個結點的下一個結點的引用指向當前結點      before.after = this;      //給定結點的上一個結點的引用指向當前結點      after.before = this;    }    //按訪問順序排序時, 記錄每次擷取的操作    void recordAccess(HashMap<K,V> m) {      LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;      //如果是按訪問順序排序      if (lm.accessOrder) {        lm.modCount++;        //先將自己從雙向鏈表中移除        remove();        //將自己放到雙向鏈表尾部        addBefore(lm.header);      }    }    ...  }  //父類put方法中會調用的該方法  void addEntry(int hash, K key, V value, int bucketIndex) {    //調用父類的addEntry方法    super.addEntry(hash, key, value, bucketIndex);    //下面操作是方便LRU緩衝的實現, 如果緩衝容量不足, 就移除最老的元素    Entry<K,V> eldest = header.after;    if (removeEldestEntry(eldest)) {      removeEntryForKey(eldest.key);    }  }  //是否刪除最老的元素, 該方法設計成要被子類覆蓋  protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {    return false;  }}

為了更加直觀,上面貼出的代碼中我將一些無關的代碼省略了,我們可以看到LinkedHashMap有一個成員變數accessOrder,該成員變數記錄了是否需要按訪問順序排序,它提供了一個構造器可以自己指定accessOrder的值。每次調用get方法擷取元素式都會調用e.recordAccess(this),該方法會將當前結點移到雙向鏈表的尾部。現在我們知道了如果accessOrder為true那麼每次get元素後都會把這個元素挪到雙向鏈表的尾部。這一步的目的是區別出最常使用的元素和不常使用的元素,經常使用的元素放到尾部,不常使用的元素放到頭部。我們再回到上面的代碼中看到每次調用addEntry方法時都會判斷是否需要刪除最老的元素。判斷的邏輯是removeEldestEntry實現的,該方法被設計成由子類進行覆蓋並重寫裡面的邏輯。注意,由於最近被訪問的結點都被挪動到雙向鏈表的尾部,所以這裡是從雙向鏈表頭部取出最老的結點進行刪除。下面例子實現了一個簡單的LRU緩衝。

public class LRUMap<K, V> extends LinkedHashMap<K, V> {    private int capacity;    LRUMap(int capacity) {    //調用父類構造器, 設定為按訪問順序排序    super(capacity, 1f, true);    this.capacity = capacity;  }    @Override  public boolean removeEldestEntry(Map.Entry<K, V> eldest) {    //當索引值對大於等於雜湊表容量時    return this.size() >= capacity;  }    public static void main(String[] args) {    LRUMap<Integer, String> map = new LRUMap<Integer, String>(4);    map.put(1, "a");    map.put(2, "b");    map.put(3, "c");    System.out.println("原創組合:" + map);    String s = map.get(2);    System.out.println("擷取元素:" + map);    map.put(4, "d");    System.out.println("插入之後:" + map);  }  }

結果如下:

註:以上全部分析基於JDK1.7,不同版本間會有差異,讀者需要注意。

以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援幫客之家。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.