面試官:如何用LinkedHashMap實現LRU

上一篇文章分析了HashMap的原理,有網友留言想看LinkedHashMap分析,今天它來了。java

LinkedHashMap是HashMap的子類,在原有HashMap數據結構的基礎上,它還維護着一個雙向鏈表連接全部entry,這個鏈表定義了迭代順序,一般是數據插入的順序。面試

LinkedHashMap結構

上圖我只畫了鏈表,其實紅黑樹節點也是同樣的,只是節點類型不同而已緩存

也就是說咱們遍歷LinkedHashMap的時候,是從head指針指向的節點開始遍歷,一直到tail指向的節點。bash

源碼

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
  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);
    }

    // 雙向鏈表頭節點
    transient LinkedHashMap.Entry<K,V> head;

    // 雙向鏈表尾節點
    transient LinkedHashMap.Entry<K,V> tail;

    // 指定遍歷LinkedHashMap的順序,true表示按照訪問順序,false表示按照插入順序,默認爲false
    final boolean accessOrder;
  }

}
複製代碼

從LinkedHashMap的定義裏面能夠看到它單獨維護了一個雙向鏈表,用於記錄元素插入的順序。這也是爲何咱們打印LinkedHashMap的時候能夠按照插入順序打印的支撐。而accessOrder屬性則指明瞭進行遍歷時是按照什麼順序進行訪問,咱們能夠經過它的構造方法本身指定順序。數據結構

public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder) {
  super(initialCapacity, loadFactor);
  this.accessOrder = accessOrder;
}
複製代碼

當accessOrder=true,訪問順序的輸出是什麼意思呢?來看下下面的一段代碼ide

LinkedHashMap<Integer,Integer> map = new LinkedHashMap<>(8, 0.75f, true);

map.put(1, 1);
map.put(2, 2);
map.put(3, 3);

map.get(2);

System.out.println(map);

複製代碼

輸出結果是ui

{1=1, 3=3, 2=2}
複製代碼

能夠看到get了的數據被放到了雙向鏈表尾部,也就是按照了訪問時間進行排序,這就是訪問順序的含義。this

在插入的時候LinkedHashMap複寫了HashMap的newNode以及newTreeNode方法,而且在方法內部更新了雙向鏈表的指向關係。spa

同時插入的時候調用了afterNodeAccess()方法以及afterNodeInsertion()方法,在HashMap中這兩個方法是空實現,而在LinkedHashMap中則有具體實現,這兩個方法也是專門給LinkedHashMap進行回調處理的。指針

afterNodeAccess()方法中若是accessOrder=true時會移動節點到雙向鏈表尾部。當咱們在調用map.get()方法的時候若是accessOrder=true也會調用這個方法,這就是爲何訪問順序輸出時訪問到的元素移動到鏈表尾部的緣由。

接下來來看看afterNodeInsertion()的實現

// evict若是爲false,則表處於建立模式,當咱們new HashMap(Map map)的時候就處於建立模式
void afterNodeInsertion(boolean evict) { // possibly remove eldest
  LinkedHashMap.Entry<K,V> first;

  // removeEldestEntry 老是返回false,因此下面的代碼不會執行。
  if (evict && (first = head) != null && removeEldestEntry(first)) {
      K key = first.key;
      removeNode(hash(key), key, null, false, true);
  }
}
複製代碼

看到這裏我有一個想法,能夠經過LinkedHashMap來實現LRU(Least Recently Used,即近期最少使用),只要知足條件,就刪除head節點。

public class LRUCache<K,V> extends LinkedHashMap<K,V> {
    
  private int cacheSize;
  
  public LRUCache(int cacheSize) {
      super(16,0.75f,true);
      this.cacheSize = cacheSize;
  }

  /**
   * 判斷元素個數是否超過緩存容量
   */
  @Override
  protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
      return size() > cacheSize;
  }
}

複製代碼

就這樣一個簡單的LRU Cache就實現了,之後面試官若是喊你給它實現一個LRU,你就這樣寫給他,若是他讓你換一種方式,你就用鏈表使用一樣的思惟給他實現一個,而後你就能夠收割offer了。

對於刪除,LinkedHashMap也一樣是在HashMap的刪除邏輯完成後,調用了afterNodeRemoval這個回調方法來更正鏈表指向關係。

其實你只要看了上一篇文章不再怕面試官問我JDK8 HashMap,再記得LinkedHashMap只是多維護了一個雙向鏈表以後,再看LinkedHashMap中關於鏈表操做的代碼就很是簡單了。

相關文章
相關標籤/搜索