【數據結構】10.java源碼關於LinkedHashMap

  • 2019 年 10 月 3 日
  • 筆記

目錄

1.LinkedHashMap的內部結構
2.LinkedHashMap構造函數
3.元素新增策略
4.元素刪除
5.元素修改和查找
6.特殊操作
7.擴容
8.總結

1.LinkedHashMap的內部結構

 

 

對象的內部結構其實就是hashmap的內部結構,但是比hashmap的內部結構node要多維護2個引用指針,用來做前置和後置鏈表

同事linkedhashmap本身還有頭鏈表節點和尾部鏈表節點

static class Entry<K,V> extends MyHashMap.Node<K,V> {      Entry<K,V> before, after;      Entry(int hash, K key, V value, Node<K,V> next) {          super(hash, key, value, next);      }  }

因為linkedhashmap是繼承自hashmap,所以hashmap有的它都有,比如上面2的n次冪,擴容策略等等
那麼就有意思了,linkedhashmap有什麼獨到的地方么???
既然是繼承自hashmap,那麼我們看看hashmap沒有的東西,或者被覆蓋重寫了的東西即可

 

2.LinkedHashMap構造函數

 

基本和hashmap一致,無法就是設置空間容量,負載因子等數據
這裡空間容量和hashmap一樣,也是取比當前容量大的最小2次冪

 

3.元素新增策略

 

就說put吧,就是完完全全調用的hashmap的 put方法。。。。暈
不過注意啊,再hashmap中有實打實大三個函數是為了linkedhashmap準備的,這個在源碼中就說明了,並且put操作就用到了其中2個

 

 

這裡可以吧之前hashmap中的這幾個函數加上了解了

還有個地方需要注意,linkedhashmap還重寫了newnode方法,這個是為了和鏈表串聯起來

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {      //每當創建一個新的鏈表節點的時候,我們調用linknodelast,吧當前添加到鏈表末尾      TestLinkedHashMap.Entry<K,V> p =              new TestLinkedHashMap.Entry<K,V>(hash, key, value, e);      linkNodeLast(p);      return p;  }    //不論這個節點是處於什麼位置,都進行添加節點  private void linkNodeLast(TestLinkedHashMap.Entry<K,V> p) {      TestLinkedHashMap.Entry<K,V> last = tail;      tail = p;      if (last == null)          head = p;      else {          p.before = last;          last.after = p;      }  }

 

接下來我們一一闡述hashmap專門為linkedhashmap預留的幾個函數

3.1 afterNodeAccess

/**   *   * @program: y2019.collection.map.TestLinkedHashMap   * @description: 只有當put進去,這個值存放到hash桶上的時候,並且這個值是之前存在的,(或者是樹狀結構),才會觸發這個函數   * @auther: xiaof   * @date: 2019/8/29 17:03   */  void afterNodeAccess(Node<K,V> e) { // move node to last      TestLinkedHashMap.Entry<K,V> last;      if (accessOrder && (last = tail) != e) {          //獲取這個節點的前置,和後置引用對象          TestLinkedHashMap.Entry<K,V> p = (TestLinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;          //把後置設置為空          p.after = null;          //如果替換的對象沒有前置節點,那麼就把當前節點當做head          if (b == null)              head = a;          else              b.after = a; //否則建立雙向鏈表數據,前置改為a            //吧a的前置改成b          if (a != null)              a.before = b;          else              last = b;            //然後吧tail指向p,這樣就把p從原來的鏈表中,斷裂開,然後拼接到tail後          if (last == null)              head = p;          else {              p.before = last;              last.after = p;          }          tail = p;          //容器類修改次數++          ++modCount;      }  }

 

3.2 afterNodeInsertion

這個是linkedhashmap再實現lrucache的時候會調用到的方法,平時沒有作用

根據evict 和 判斷是否需要刪除最老插入的節點,後面我們實現lrucache的時候再詳細了解

 

4.元素刪除

 

Linkedhashmap的刪除操作和hashmap一致,但是還有一個函數被重寫了,就是這裡有點不一樣

其實操作就是,linkedhashmap因為是一個雙向鏈表,所以在刪除的時候就是做一個對雙向鏈表進行刪除的操作

這個方法就是
AfterNodeRemoval 把從hashmap中刪除的元素,斷開雙向鏈表的連接

 

//把從hashmap中刪除的元素,斷開雙向鏈表的連接  void afterNodeRemoval(Node<K, V> e) { // unlink      TestLinkedHashMap.Entry<K, V> p =              (TestLinkedHashMap.Entry<K, V>) e, b = p.before, a = p.after;      p.before = p.after = null;      if (b == null)          head = a;      else          b.after = a;      if (a == null)          tail = b;      else          a.before = b;  }

 

5.元素修改和查找

對於查找get元素,這裡linkedhashmap就直接重寫了,但是裡面調用getnode其實還是hashmap那一套,不過就是多了個判斷accessOrder參數,如果為true就會調用afterNodeAccess

這個方法前面有講到

6.特殊操作

 

6.1 containsValue

因為是鏈表的緣故,所以這裡是直接循環遍歷鏈表一次即可

 

public boolean containsValue(Object value) {      for (TestLinkedHashMap.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;  }

 

而hashmap呢?

public boolean containsValue(Object value) {          Node<K,V>[] tab; V v;          if ((tab = table) != null && size > 0) {              //先循環hash桶              for (int i = 0; i < tab.length; ++i) {                  //然後遍歷鏈表                  for (Node<K,V> e = tab[i]; e != null; e = e.next) {                      if ((v = e.value) == value ||                              (value != null && value.equals(v)))                          return true;                  }              }          }          return false;      }

6.2 實現LRUCACHE

要實現lru首先要明白這是個什麼?
近期最少使用演算法。 記憶體管理的一種頁面置換演算法,對於在記憶體中但又不用的數據塊(記憶體塊)叫做LRU,作業系統會根據哪些數據屬於LRU而將其移出記憶體而騰出空間來載入另外的數據。

為什麼用linkedhashmap呢?因為這個容器事實是一個雙向鏈表,而且裡面帶上參數的構造函數的時候,前面用的get方法會調用到afterNodeAccess方法,這個方法會吧最近get的數據重新指引向鏈表末尾

基於這點我們只要吧accessOrder設置為true即可

 

package y2019.collection.map;    import java.util.Iterator;  import java.util.LinkedHashMap;  import java.util.Map;  import java.util.Set;    /**   * @ProjectName: cutter-point   * @Package: y2019.collection.map   * @ClassName: TestLRUCache   * @Author: xiaof   * @Description: 實現lru (最近最不常使用)快取   * 獲取數據(get)和寫入數據(set)。   * 獲取數據get(key):如果快取中存在key,則獲取其數據值(通常是正數),否則返回-1。   * 寫入數據set(key, value):如果key還沒有在快取中,則寫入其數據值。   * 當快取達到上限,它應該在寫入新數據之前刪除最近最少使用的數據用來騰出空閑位置。   * @Date: 2019/9/3 16:42   * @Version: 1.0   */  public class TestLRUCache<K, V> {        LinkedHashMap<K, V> cache = null;      int cacheSize;        public TestLRUCache(int cacheSize) {          //默認負載因子取0.75          this.cacheSize = (int) Math.ceil(cacheSize / 0.75f) + 1;//向上取整數          cache = new LinkedHashMap<K, V>(this.cacheSize, 0.75f, true) {              @Override              protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {                  //這裡有個關鍵的負載操作,因為是lru,所以當長度超了的時候,不是擴容,而是吧鏈表頭幹掉                  System.out.println("size=" + this.size());                  return this.size() > cacheSize;              }          };      }        public V get(K key) {          return cache.get(key);      }        public V set(K key, V value) {          return cache.put(key, value);      }        public void setCacheSize(int cacheSize) {          this.cacheSize = cacheSize;      }        public void printCache(){          for(Iterator it = cache.entrySet().iterator(); it.hasNext();){              Map.Entry<K,V> entry = (Map.Entry<K, V>)it.next();              if(!"".equals(entry.getValue())){                  System.out.println(entry.getKey() + "t" + entry.getValue());              }          }          System.out.println("------");      }        public void PrintlnCache(){          Set<Map.Entry<K,V>> set = cache.entrySet();          for(Map.Entry<K,V> entry : set){              K key = entry.getKey();              V value = entry.getValue();              System.out.println("key:"+key+"value:"+value);          }        }        public static void main(String[] args) {          TestLRUCache<String,Integer> lrucache = new TestLRUCache<String,Integer>(3);          lrucache.set("aaa", 1);          lrucache.printCache();          lrucache.set("bbb", 2);          lrucache.printCache();          lrucache.set("ccc", 3);          lrucache.printCache();          lrucache.set("ddd", 4);          lrucache.printCache();          lrucache.set("eee", 5);          lrucache.printCache();          System.out.println("這是訪問了ddd後的結果");          lrucache.get("ddd");          lrucache.printCache();          lrucache.set("fff", 6);          lrucache.printCache();          lrucache.set("aaa", 7);          lrucache.printCache();      }      }

 

7.擴容

參考hashmap

 

8.總結我們重點放在lrucache上吧

 藉助linkedhashmap實現lru,重點就是再大小範圍超出的時候進行刪除頭結點,而不是擴容

 

參考:
https://blog.csdn.net/zxt0601/article/details/77429150
https://www.jianshu.com/p/d76a78086c3a