1.LinkedHashMap的內部結構
2.LinkedHashMap構造函數
3.元素新增策略
4.元素刪除
5.元素修改和查找
6.特殊操做
7.擴容
8.總結java
對象的內部結構其實就是hashmap的內部結構,可是比hashmap的內部結構node要多維護2個引用指針,用來作前置和後置鏈表node
同事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沒有的東西,或者被覆蓋重寫了的東西便可緩存
基本和hashmap一致,沒法就是設置空間容量,負載因子等數據
這裏空間容量和hashmap同樣,也是取比當前容量大的最小2次冪ide
就說put吧,就是完徹底全調用的hashmap的 put方法。。。。暈
不過注意啊,再hashmap中有實打實大三個函數是爲了linkedhashmap準備的,這個在源碼中就說明了,而且put操做就用到了其中2個函數
這裏能夠吧以前hashmap中的這幾個函數加上了解了this
還有個地方須要注意,linkedhashmap還重寫了newnode方法,這個是爲了和鏈表串聯起來spa
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預留的幾個函數操作系統
/** * * @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; } }
這個是linkedhashmap再實現lrucache的時候會調用到的方法,平時沒有做用.net
根據evict 和 判斷是否須要刪除最老插入的節點,後面咱們實現lrucache的時候再詳細瞭解
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; }
對於查找get元素,這裏linkedhashmap就直接重寫了,可是裏面調用getnode其實仍是hashmap那一套,不過就是多了個判斷accessOrder參數,若是爲true就會調用afterNodeAccess
這個方法前面有講到
由於是鏈表的緣故,因此這裏是直接循環遍歷鏈表一次便可
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; }
要實現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(); } }
參考hashmap
藉助linkedhashmap實現lru,重點就是再大小範圍超出的時候進行刪除頭結點,而不是擴容
參考:https://blog.csdn.net/zxt0601/article/details/77429150https://www.jianshu.com/p/d76a78086c3a