日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JDK1.8源码分析:LinkedHashMap与LRU缓存设计思路

發布時間:2024/1/18 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JDK1.8源码分析:LinkedHashMap与LRU缓存设计思路 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

概述

  • LinkedHashMap繼承于HashMap,在HashMap的基礎上,新增了兩個特性:
  • 支持以節點的插入順序來迭代該map內的所有節點;
  • 支持緩存設計中LRU的特性,即LinkedHashMap支持按訪問順序來排序節點,具體在內部實現為如果開啟了這個特性,則每次通過get方法訪問了一個節點,則該節點會被移動到內部的雙向鏈表的末尾,故雙向鏈表的頭結點是最近最少訪問的節點,尾節點為剛剛訪問過的節點,中間節點依次類推。
    • 以上兩個特性是互斥存在的,默認是以節點插入順序來排序節點,可以通過設置構造函數中的accessOrder為true來開啟按節點訪問順序排序。

      /*** Constructs an empty <tt>LinkedHashMap</tt> instance with the* specified initial capacity, load factor and ordering mode.** @param initialCapacity the initial capacity* @param loadFactor the load factor* @param accessOrder the ordering mode - <tt>true</tt> for* access-order, <tt>false</tt> for insertion-order* @throws IllegalArgumentException if the initial capacity is negative* or the load factor is nonpositive*/ public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder; }
    • 以上兩個特性都是基于在LinkedHashMap中額外維護了一個雙向鏈表來實現。

    • 以上兩個特性都是在迭代器中體現,具體為entrySet方法,keySet方法,values方法,在for循環遍歷這些方法返回的集合。

    數據結構與核心字段

    • LinkedHashMap繼承于HashMap,節點數據也是存儲在HashMap的哈希表table數組中。

    • 為了支持以上兩個特性,在LinkedHashMap內部額外維護了一個雙向鏈表的數據結構:對HashMap的節點Node進行了拓展,定義了雙向鏈表的節點數據結構Entry,增加了before和after兩個指針,分別為指向前節點和后節點,從而實現雙向鏈表的特性。

    • 如下為在LinkedHashMap內部定義的雙向鏈表的鏈表節點Entry,雙向鏈表的頭結點指針head,雙向鏈表的尾節點指針tail:

      // 雙向鏈表數據結構 /*** HashMap.Node subclass for normal LinkedHashMap entries.*/ 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);} }/*** The head (eldest) of the doubly linked list.*/ transient LinkedHashMap.Entry<K,V> head;/*** The tail (youngest) of the doubly linked list.*/ transient LinkedHashMap.Entry<K,V> tail;
    • 注意LinkedHashMap在HashMap的哈希表table數組內的鏈表的鏈表數據存儲節點,使用的是這個拓展的Entry類;而對于紅黑樹節點,則還是使用HashMap中定義的。

    • 由于雙向鏈表節點是LinkedHashMap額外的維護的結構,所以在增刪改父類HashMap中的哈希表table數組中的數據節點時,需要回調LinkedHashMap中的對該雙向鏈表增刪改的方法來保持數據同步。

    accessOrder:訪問順序排序開關

    • 在LinkedHashMap中定義了accessOrder字段來控制是否以訪問順序排序雙向鏈表的節點:默認為false,不使用,使用雙向鏈表節點插入順序來排序。

      /*** The iteration ordering method for this linked hash map: <tt>true</tt>* for access-order, <tt>false</tt> for insertion-order.** @serial*/ final boolean accessOrder;
    • accessOrder主要是在LinkedHashMap的get方法中使用,即在訪問某個key對應的節點時,判斷是否需要將在雙向鏈表中對應的節點移動到雙向鏈表末尾,具體在以下分析。

    核心方法

    由于LinkedHashMap繼承于HashMap,在內部也是使用HashMap的哈希表table數組來存儲數據,LinkedHashMap主要是覆蓋HashMap的相關方法或者實現HashMap的增刪改回調方法,來對自身的雙向鏈表進行調整,或者是利用自身維護的雙向鏈表來對HashMap中的相關方法進行重寫優化。

    覆蓋HashMap的方法

    • 新增節點:newNode方法,覆蓋HashMap的新增節點方法,返回的是LinkedHashMap內部定義的Entry節點,故在HashMap的哈希表table數組內部的鏈表的鏈表節點類型為Entry了。同時調用linkNodeLast方法將該節點放到內部的雙向鏈表的末尾。

      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; }// 將該節點放到雙向鏈表的末尾 // link at the end of list private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {LinkedHashMap.Entry<K,V> last = tail;tail = p;if (last == null)head = p;else {p.before = last;last.after = p;} }
    • 訪問節點:get方法,在內部調用了HashMap的getNode方法來從HashMap的哈希表table數組查找該指定key對應的節點。額外增加通過accessOrder的判斷來決定是否對自身的雙向鏈表節點進行調整。

      /*** Returns the value to which the specified key is mapped,* or {@code null} if this map contains no mapping for the key.** <p>More formally, if this map contains a mapping from a key* {@code k} to a value {@code v} such that {@code (key==null ? k==null :* key.equals(k))}, then this method returns {@code v}; otherwise* it returns {@code null}. (There can be at most one such mapping.)** <p>A return value of {@code null} does not <i>necessarily</i>* indicate that the map contains no mapping for the key; it's also* possible that the map explicitly maps the key to {@code null}.* The {@link #containsKey containsKey} operation may be used to* distinguish these two cases.*/ public V get(Object key) {Node<K,V> e;// getNode為在HashMap中定義的方法if ((e = getNode(hash(key), key)) == null)return null;// 判斷是否以訪問順序排序雙向鏈表節點if (accessOrder)afterNodeAccess(e);return e.value; }// 將當前訪問的節點,調整到雙向鏈表的末尾,實現按訪問順序排序的功能 void afterNodeAccess(Node<K,V> e) { // move node to lastLinkedHashMap.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;elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;++modCount;} }
    • containsValue:判斷map中是否存在指定value的節點,重寫了hashMap的containsValue方法,利用雙向鏈表來查找。在HashMap中需要遍歷哈希表table數組,然后遍歷數組中每個元素對應的鏈表,即從鏈表頭開始一個個比較。

      /*** Returns <tt>true</tt> if this map maps one or more keys to the* specified value.** @param value value whose presence in this map is to be tested* @return <tt>true</tt> if this map maps one or more keys to the* specified value*/ public boolean containsValue(Object value) {for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {V v = e.value;if (v == value || (value != null && value.equals(v)))return true;}return false; }
    • 清空數據方法:clear,主要是調用父類HashMap的clear來完成對哈希表table數組內部所有數據的清空,在LinkedHashMap中需要將雙向鏈表的頭指針和尾指針均置為null。

      /*** {@inheritDoc}*/ public void clear() {super.clear();head = tail = null; }

    HashMap的增刪改的回調方法

    以上方法由于HashMap沒有提供回調方法來進行拓展,故需要在LinkedHashMap中顯式重寫來加入對雙向鏈表的操作。在HashMap中對于增刪改節點對應了回調方法,故可以在LinkedHashMap中實現這些回調方法即可。

    • 如下為在HashMap中聲明的回調方法:

      // Callbacks to allow LinkedHashMap post-actions void afterNodeAccess(Node<K,V> p) { } void afterNodeInsertion(boolean evict) { } void afterNodeRemoval(Node<K,V> p) { }
    • afterNodeAccess:節點訪問回調,主要在get方法中調用,可以參見以上get方法的分析。

    • afterNodeInsertion:節點插入回調,主要是在HashMap的putVal方法實現中最后調用,即在往HashMap的哈希表table數組插入數據相關查找完成后,最后調用afterNodeInsertion。LinkedHashMap的afterNodeInsertion回調實現如下:

      void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;// 判斷是否刪除最近最少訪問的節點if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;// removeNode內部會調用afterNodeRemoval方法來調整該雙向鏈表removeNode(hash(key), key, null, false, true);} }

      主要用于在基于LinkedHashMap來實現緩存時,實現緩存的LRU特性使用。

    • afterNodeRemoval:在HashMap刪除某個節點時,回調afterNodeRemoval方法。LinkedHashMap的實現為在自身維護的雙向鏈表中刪除對應的鏈表節點:

      // 在HashMap中的鏈表節點e刪除后,同步調整該雙向鏈表,刪除該節點 void afterNodeRemoval(Node<K,V> e) { // unlinkLinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.before = p.after = null;if (b == null)head = a;elseb.after = a;if (a == null)tail = b;elsea.before = b; }

    迭代器

    • 在LinkedHashMap中,迭代器相關的操作是基于自身的雙向鏈表,而不是父類HashMap的哈希表table數組來實現的,故迭代順序是基于雙向鏈表的順序實現的,即以插入順序(從前到后:最先插入->最后插入)排序或者訪問順序排序(從前到后:最近最少訪問 -> 剛剛訪問)。

    • LinkedHashMap的方法包括:entrySet方法,keySet方法,values方法

    • LinkedHashMap的迭代器定義:主要在構造函數中將next初始化為雙向鏈表的頭結點head。

      // Iteratorsabstract class LinkedHashIterator {LinkedHashMap.Entry<K,V> next;LinkedHashMap.Entry<K,V> current;int expectedModCount;LinkedHashIterator() {// 初始化為雙向鏈表頭結點headnext = head;expectedModCount = modCount;current = null;}public final boolean hasNext() {return next != null;}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;}public final void remove() {Node<K,V> p = current;if (p == null)throw new IllegalStateException();if (modCount != expectedModCount)throw new ConcurrentModificationException();current = null;K key = p.key;removeNode(hash(key), key, null, false, false);expectedModCount = modCount;} }

    LRU緩存

    • 由于LinkedHashMap支持按訪問順序排序雙向鏈表的特性,故可以基于LinkedHashMap來實現一個LRU緩存,具體為拓展LinkedHashMap,在緩存類中,重寫removeEldestEntry方法來定義刪除最近最少訪問的節點的條件。

      /*** Returns <tt>true</tt> if this map should remove its eldest entry.* This method is invoked by <tt>put</tt> and <tt>putAll</tt> after* inserting a new entry into the map. It provides the implementor* with the opportunity to remove the eldest entry each time a new one* is added. This is useful if the map represents a cache: it allows* the map to reduce memory consumption by deleting stale entries.** <p>Sample use: this override will allow the map to grow up to 100* entries and then delete the eldest entry each time a new entry is* added, maintaining a steady state of 100 entries.* <pre>* private static final int MAX_ENTRIES = 100;** protected boolean removeEldestEntry(Map.Entry eldest) {* return size() &gt; MAX_ENTRIES;* }* </pre>** <p>This method typically does not modify the map in any way,* instead allowing the map to modify itself as directed by its* return value. It <i>is</i> permitted for this method to modify* the map directly, but if it does so, it <i>must</i> return* <tt>false</tt> (indicating that the map should not attempt any* further modification). The effects of returning <tt>true</tt>* after modifying the map from within this method are unspecified.** <p>This implementation merely returns <tt>false</tt> (so that this* map acts like a normal map - the eldest element is never removed).** @param eldest The least recently inserted entry in the map, or if* this is an access-ordered map, the least recently accessed* entry. This is the entry that will be removed it this* method returns <tt>true</tt>. If the map was empty prior* to the <tt>put</tt> or <tt>putAll</tt> invocation resulting* in this invocation, this will be the entry that was just* inserted; in other words, if the map contains a single* entry, the eldest entry is also the newest.* @return <tt>true</tt> if the eldest entry should be removed* from the map; <tt>false</tt> if it should be retained.*/ protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false; }
    • 由以上分析可知,removeEldestEntry主要是在HashMap的新增節點的回調afterNodeInsertion中調用。在LinkedHashMap的afterNodeInsertion方法實現如下:

      void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;// 判斷是否刪除最近最少訪問的節點if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;// removeNode內部會調用afterNodeRemoval方法來調整該雙向鏈表removeNode(hash(key), key, null, false, true);} }
    • 在afterNodeInsertion中,head頭結點就是最近最少訪問的節點,故在該緩存類中,需要設置accessOrder為true來開啟按訪問順序排序;
    • 在afterNodeInsertion中會調用HashMap的removeNode方法來刪除雙向鏈表頭結點head對應的哈希表table的鏈表的鏈表節點,在HashMap的removeNode會回調LinkedHashMap的afterNodeRemoval來刪除LinkedHashMap內部的雙向鏈表的鏈表節點;
    • 故在繼承了LinkedHashMap的緩存類只需實現removeEldestEntry方法即可。
    • removeEldestEntry的方法實現例子:

      public class LRUCache extends LinkedHashMap {private static final int MAX_ENTRIES = 100;public LRUCache(int initialCapacity, float loadFactor) {// 第三個參數為accessOrdersuper(initialCapacity, loadFactor, true);}protected boolean removeEldestEntry(Map.Entry eldest) {return size() > MAX_ENTRIES;} }

    總結

    以上是生活随笔為你收集整理的JDK1.8源码分析:LinkedHashMap与LRU缓存设计思路的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。