1. 程式人生 > >【資料結構】10.java原始碼關於LinkedHashMap

【資料結構】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

&n