Java集合——LinkedHashMap

簡述

LinkedHashMap繼承了HashMap,其操做與HashMap相似,結構也差很少。與HashMap最大區別就是經過節點Entry增長了before和after屬性來維護順序使其有序。示例根據插入順序排序:node

public static void main(String[] args) {
        System.out.println("**********HashMap***********");
        Map hashMap = new HashMap();
        hashMap.put("Marvel", "漫威");
        hashMap.put("Deadpool", "死侍");
        hashMap.put("Hulk", "綠巨人");
        hashMap.put("Thor", "雷神");
        hashMap.put("Wolverine", "金剛狼");
        for (Iterator it = hashMap.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry obj = (Map.Entry)it.next();
            System.out.println(obj.getKey() + "-" +obj.getValue());
        }
        System.out.println("**********LinkedHashMap***********");
        Map linkedHashMap = new LinkedHashMap();
        linkedHashMap.put("Marvel", "漫威");
        linkedHashMap.put("Deadpool", "死侍");
        linkedHashMap.put("Hulk", "綠巨人");
        linkedHashMap.put("Thor", "雷神");
        linkedHashMap.put("Wolverine", "金剛狼");
        for (Iterator it = linkedHashMap.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry obj = (Map.Entry)it.next();
            System.out.println(obj.getKey() + "-" +obj.getValue());
        }
    }
複製代碼

輸出:緩存

**********HashMap***********
    Thor-雷神
    Deadpool-死侍
    Wolverine-金剛狼
    Marvel-漫威
    Hulk-綠巨人
    **********LinkedHashMap***********
    Marvel-漫威
    Deadpool-死侍
    Hulk-綠巨人
    Thor-雷神
    Wolverine-金剛狼 
複製代碼

源碼分析

LinkedHashMap字段

final boolean accessOrder;                //是否按照訪問順序,true:訪問順序,false:插入順序
    transient LinkedHashMap.Entry head;       //雙向鏈表頭節點
    transient LinkedHashMap.Entry tail;       //雙向鏈表尾結點
    
    /**
     * 節點類繼承了HashMap.Node,改爲雙向鏈表
     * next表示桶上鍊接的Entry順序
     * before、after插入先後,插入順序(維護雙向鏈表)
     */
    static class Entry extends HashMap.Node {
        Entry before, after;
    }    
複製代碼

構造方法

前四個默認插入排序,最後一個可指定排序,accessOrder爲true時訪問順序,false時插入順序ide

public LinkedHashMap() {
        super();
        accessOrder = false;
    }
    
    //指定初始化容量
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }
    
    //指定初始化容量和負載因子
    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }
    
    //利用另外一個map來構建
    public LinkedHashMap(Map m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }
    
    //指定初始化容量、負載因子和是否按照訪問順序
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }
複製代碼

put方法

LinkedHashMap沿用了HashMap的put方法,不太重寫了其newNode()、afterNodeAccess()、afterNodeInsertion()方法源碼分析

Node newNode(int hash, K key, V value, Node e) {
        //調用LinkedHashMap的entry構造方法
        LinkedHashMap.Entry p =
            new LinkedHashMap.Entry(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
    
    /**
     * 將新增節點置於鏈表尾部
     */
    private void linkNodeLast(LinkedHashMap.Entry p) {
        //獲取當前鏈表尾部節點
        LinkedHashMap.Entry last = tail;
        //將p設爲尾部節點
        tail = p;
        //若當前集合爲空,p既是頭節點又是尾節點
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }
複製代碼

從上面的源碼能夠看出,linkedHashMap額外維護了一個雙向鏈表。
再來看看afterNodeAccess()方法,在put方法若當前集合存在key對象進行替換value時會調用afterNodeAccess:this

/**
     * 將當前被訪問的節點移至雙向鏈表尾部
     */
    void afterNodeAccess(Node e) { // move node to last
        LinkedHashMap.Entry last;
        //若accessOrder爲true且原尾節點不是節點e
        if (accessOrder && (last = tail) != e) {
            //將節點e強轉爲雙向鏈表節點p,獲取p插入先後的節點
            LinkedHashMap.Entry p =
                (LinkedHashMap.Entry)e, b = p.before, a = p.after;
            //由於須要將e置於鏈表尾部,因此將其after屬性設爲null
            p.after = null;
            //對於雙向鏈表,若p的前驅節點爲空,頭節點設爲p的後繼
            if (b == null)
                head = a;
            else
            //不然將p前驅節點的後繼節點設爲p的後繼節點
                b.after = a;
            //若p的後繼節點不爲null,將p的後繼節點的前驅節點設爲p的前驅節點    
            if (a != null)
                a.before = b;
            else
            //不然將p的前驅節點設爲尾結點
                last = b;
            //若原尾節點爲空,將p設爲頭節點    
            if (last == null)
                head = p;
            else {
            //不然將p的前驅節點改成原尾節點,原尾節點的後繼節點改成p
                p.before = last;
                last.after = p;
            }
            //尾節點改成p
            tail = p;
            ++modCount;
        }
    } 
複製代碼

在put方法中新增節點狀況下最後會調用afterNodeInsertion()方法,源碼以下:spa

/**
     * 刪除雙向鏈表頭節點
     */
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }
    
    //默認返回false
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return false;
    }
複製代碼

void afterNodeInsertion(boolean evict)以及boolean removeEldestEntry(Map.Entry<K,V> eldest)是構建LruCache須要的回調,在LinkedHashMap裏能夠忽略它們.net

get方法

LinkedHashMap重寫了其get()方法code

public V get(Object key) {
        Node e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }
複製代碼

相對於HashMap的get操做,linkedHashMap多了一步操做,若accessOrder爲true會調用afterNodeAccess()方法。afterNodeAccess()方法上面已經說起,須要注意的是在此方法會修改modCount即當迭代LinkedHashMap,若同時查詢訪問數據,會致使fail-fast,由於迭代順序變了cdn

remove方法

LinkedHashMap沿用了HashMap的remove()方法,不太重寫了其afterNodeRemoval()方法對象

/**
     * 將節點e從雙向鏈表中刪除
     */
    void afterNodeRemoval(Node e) { // unlink
        LinkedHashMap.Entry p =
            (LinkedHashMap.Entry)e, b = p.before, a = p.after;
        //待刪除節點p的前驅後繼節點都置空    
        p.before = p.after = null;
        //若前驅節點爲空,則將p後繼節點設爲頭節點
        if (b == null)
            head = a;
        //不然將p後繼節點設爲p前驅節點的後繼節點    
        else
            b.after = a;
        //若p後繼節點爲空,則將p的前驅節點設爲尾結點    
        if (a == null)
            tail = b;
        //不然p前驅節點設爲p後繼節點的前驅節點    
        else
            a.before = b;
    } 
複製代碼

用LinkedHashMap實現緩衝機制

FIFO

FIFO(First In First Out):先入先出,和隊列同樣。舉個生活例子,超市購物收銀臺結帳先排隊的客戶先結帳離開
FIFO實現:LinkedHashMap默認(accessOrder爲false)就是按存儲的數據排序,知足先進先出,示例以下:

public class FIFOCache extends LinkedHashMap {

    private final int maxSize;//限制數據

    public FIFOCache(int maxSize) {
        super();//調用父類默認構造方法,accessOrder爲false
        this.maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > maxSize;
    }

    public static void main(String[] args) {
        Map fifoCache = new FIFOCache(10);//限定10個
        for (int i = 0; i < 10; i++) {
            fifoCache.put(i, i);
        }
        System.out.println("初始狀況:" + fifoCache.toString());

        fifoCache.put(6, 6);//訪問已存在數據
        System.out.println("已存在數據被訪問後:" + fifoCache.toString());

        fifoCache.put(10, 10);
        System.out.println("新增一個數據後:" + fifoCache.toString());
    }
} 
複製代碼

輸出:

初始狀況:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
已存在數據被訪問後:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
新增一個數據後:{1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10} 
複製代碼

從輸出結果中看,其知足FIFO規則,按插入順序進行排序,若命中緩存中的任意數據也不會破壞先進先出規則。若新增了一個緩存外的數據會把最早插入的數據移除

LRU

public class LRUCache extends LinkedHashMap {

    private final int maxSize;

    public LRUCache(int maxSize){
        super(maxSize, 0.75f, true);
        this.maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > maxSize;
    }

    public static void main(String[] args) {
        Map fifoCache = new LRUCache(10);//限定10個
        for (int i = 0; i < 10; i++) {
            fifoCache.put(i, i);
        }
        System.out.println("初始狀況:" + fifoCache.toString());

        fifoCache.put(6, 6);//訪問已存在數據
        fifoCache.get(3);
        System.out.println("已存在數據被訪問後:" + fifoCache.toString());

        fifoCache.put(10, 10);
        System.out.println("新增一個數據後:" + fifoCache.toString());
    }
}
複製代碼

輸出:

初始狀況:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
已存在數據被訪問後:{0=0, 1=1, 2=2, 4=4, 5=5, 7=7, 8=8, 9=9, 6=6, 3=3}
新增一個數據後:{1=1, 2=2, 4=4, 5=5, 7=7, 8=8, 9=9, 6=6, 3=3, 10=10}
複製代碼

從輸出結果中能夠看出,符合LRU規則

總結

LinkedHashMap幾乎與HashMap同樣,其最大區別就是節點類多了before和after屬性額外維護雙向鏈表用來實現插入順序(默認)或訪問順序排序

參考

https://blog.csdn.net/u012403290/article/details/68926201

相關文章
相關標籤/搜索