LinkedHashMap原理及LRU

基於jdk 1.6 源碼分析

1.結構

在這裏插入圖片描述

繼承了hashmap,重寫了部分方法來實現有序

2.有序原理

首先看hashmap 的數據結構
在這裏插入圖片描述

每個元素只跟在相同位置的元素有關係

linkedhashmap 的數據結構
在這裏插入圖片描述

entry 元素 除了有next 指針,還有Before,after 指針 指定前後結點的關係。
新增了一個header 元素,作爲entry 雙向鏈表的頭結點

3.源碼分析

要想實現有序,無非是在保存或者遍歷元素的時候更新元素在鏈表中的位置。

3.1 首先看構造方法
    public LinkedHashMap() {
	     super();
        accessOrder = false;
    }
調用hashmap的構造方法,然後設置了一個accessOrder 屬性值。
看下這個屬性值的解釋:
/**
 * The iteration ordering method for this linked hash map: <tt>true</tt>
 * for access-order, <tt>false</tt> for insertion-order.
 *
 * @serial
 */

迭代順序
true:訪問順序
false:插入順序

再看父類構造器做了什麼:

 this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();

一系列初始化操作,最後調了init方法,該方法在hashmap中實現爲空,linkedhashmap 做了實現。看下源碼  
    void init() {
        header = new Entry<K,V>(-1, null, null, null);
        header.before = header.after = header;
     }
創建了一個雙向鏈表的頭結點。
3.2 看 hashmap 的put方法
if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
    }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
      
       如果已經存在相同key的元素,替換之,調用e.recordAccess(this) 方法。
       如果table指定位置不存在元素或者有元素但是key不同,調用 addEntry(hash, key, value, i) 方法添加新元素.
       接下來看下這兩個方法的實現
3.3 LinkedHashMap Entry.recordAccess 方法

hashmap中的實現爲空,linkedhashmap做了實現。

 void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) { //如果設置了按照訪問順序遍歷
                lm.modCount++;
                remove();//,刪除從鏈表中刪除當前節點
                addBefore(lm.header);//將當前元素插入鏈表末尾
            }
   }
   
    private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry; //header
            before = existingEntry.before;//header 的before 也就是尾節點
            before.after = this;//當前尾節點的後一個節點指向當前節點
            after.before = this;//頭結點的上一個節點指向當前節點
   }
3.4 LinkedHashMap addEntry 方法
 void addEntry(int hash, K key, V value, int bucketIndex) {
        createEntry(hash, key, value, bucketIndex);//創建元素,將元素添加到鏈表末尾

        // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {//判斷是否要移除最老的元素,實現LRU相關,默認爲false
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
  }

createEntry 方法實現

void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
	Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
        table[bucketIndex] = e;//插入bucketIndex 位置entry鏈表的第一位
        e.addBefore(header);//插入entry 雙向鏈表尾部
        size++;
 }

至此我們瞭解了LinkedHashMap 如何實現元素之間有序:添加元素的同時加入雙向鏈表

  文章開頭講到LinkedHashMap有個 accessOrder 可以控制map遍歷時是按照訪問順序還是插入順序 遍歷,接下來先看下涉及元素訪問的方法get(put方法也算,上面分析過),然後看下LinkedHashMap 的迭代器。

3.5 LinkedHashMap get 方法
public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);//調用次方法,前面分析過如果是設置按照訪問順序遍歷,將當前元素放入雙向列表的尾部,表示最新訪問
        return e.value;
 }
3.6 LinkedHashMap LinkedHashIterator

LinkedHashMap 定義了一個內部類LinkedHashIterator,直接遍歷雙向鏈表中的元素,從而實現按照訪問順序遍歷或者插入順序遍歷

private abstract class LinkedHashIterator<T> implements Iterator<T> {
	Entry<K,V> nextEntry    = header.after; //從雙向鏈表的頭節點開始遍歷
	Entry<K,V> lastReturned = null;

	/**
	 * The modCount value that the iterator believes that the backing
	 * List should have.  If this expectation is violated, the iterator
	 * has detected concurrent modification.
	 */
	int expectedModCount = modCount;

	public boolean hasNext() {
            return nextEntry != header;
	}

	public void remove() {
	    if (lastReturned == null)
		throw new IllegalStateException();
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();

            LinkedHashMap.this.remove(lastReturned.key);
            lastReturned = null;
            expectedModCount = modCount;
	}

	Entry<K,V> nextEntry() {
	    if (modCount != expectedModCount)
		throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();

            Entry<K,V> e = lastReturned = nextEntry;
            nextEntry = e.after;
            return e;
	}
    }

至此 LinkedHashMap 實現有序遍歷的原理分析完畢。接下來看看如何實現一個LRU 緩存

4.LRU緩存

  上文提到過linkedhashmap 添加元素的方法 addEntry 中有個判斷 是否要刪除最老的元素,如果返回true 會刪除雙向鏈表中最老的元素,也就是頭節點後面的元素。重寫removeEldestEntry 即可實現LRU緩存.
例:

public class MyLRUMap<K,V> extends LinkedHashMap<K,V> {
    private static final int MAX_SIZE = 4;

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

    public static void main(String args[]) {
        MyLRUMap<String,String> map = new MyLRUMap();
        map.put("a","1");
        map.put("c","2");
        map.put("d","3");
        map.put("e","4");
        System.err.println(map);
        map.put("f","5");
        System.err.println(map);
    }
}
結果
{a=1, c=2, d=3, e=4}
{c=2, d=3, e=4, f=5}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章