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

目錄


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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章