java基礎解析系列(四)---LinkedHashMap的原理及LRU算法的實現


java基礎解析系列(四)---LinkedHashMap的原理及LRU算法的實現

實驗

遍歷HashMap

public static void main(String[] args)
	{
		Map<String, String> map=new HashMap<String,String>();
	    map.put("white", "小白");
	    map.put("black", "小黑");
	    map.put("red", "小紅");
	    map.put("yellow", "小黃");
	    map.get("yellow");
	    map.get("red");
	    map.get("black");
	    map.get("white");
	    
	    Iterator iter = map.entrySet().iterator();
	    while (iter.hasNext()) {
	        Map.Entry entry = (Map.Entry) iter.next();
	        System.out.println(entry.getKey() + "=" + entry.getValue());
	    }
}
  • 結果發現,hashmap遍歷出來的是無序的

換成LinkedHashMap

	Map<String, String> map=new LinkedHashMap<String,String>();
  • 結果發現,遍歷出來的順序是按找插入的順序

改變LinkedHashmap的參數

Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true);
  • 結果發現,遍歷出來的順序是按照訪問的順序的來的

分析

與HashMap的關系

  • LinkedHashMap可以看做是LinkedList+hashmap的組合
  • LinkedHashMap是hashmap的子類
public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
  • LinkedHashMap中的Entry有before和after兩個域,這是用來實現雙向鏈表的
 private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;

        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }

構造方法

LinkedHashMap
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder)構造一個帶指定初始容量、加載因子和排序模式的空 LinkedHashMap 實例。 

參數:
initialCapacity - 初始容量
loadFactor - 加載因子
accessOrder - 排序模式 - 對於訪問順序,為 true;對於插入順序,則為 false 
拋出: 
IllegalArgumentException - 如果初始容量為負或者加載因子為非正

  • 從api可以看出,有一個參數accessOrder,默認情況下該參數為false,為false的時候,順序為插入順序,如果為true的話,順序為訪問順序
public LinkedHashMap() {
      super();
      accessOrder = false;
  }
  void init() {
      header = new Entry<K,V>(-1, null, null, null);
      header.before = header.after = header;
  }
  • 從這里看出,還未put的時候就創建了一個header

put方法

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    //如果table數組該下標的內容不為空,遍歷鏈表
    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;
        }
    }
//如果沒有該key
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
  • put 方法繼承自hashmap的,不同的是addEntry被重寫了,如果put的鍵值對中是新的,那么執行addEntry方法

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)) {
        removeEntryForKey(eldest.key);
    } else {
        if (size >= threshold)
            resize(2 * table.length);
    }
}
  • addEntry方法先調用createEntry方法

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;
    e.addBefore(header);
    size++;
}
  • 這里將Entry放到table之后,還執行了一個addBefore方法

addBefore方法

private void addBefore(Entry<K,V> existingEntry) {
    after  = existingEntry;
    before = existingEntry.before;
    before.after = this;
    after.before = this;
}
  • 這個方法用於維護雙向鏈表,可以自己畫一個圖看一下

回到addEntry方法,執行完createEntry方法后,下面的部分代碼

 // Remove eldest entry if instructed, else grow capacity if appropriate
    Entry<K,V> eldest = header.after;
    if (removeEldestEntry(eldest)) {
        removeEntryForKey(eldest.key);
    } else {
        if (size >= threshold)
            resize(2 * table.length);
    }
  • 第一句注釋的意思是如果指示那么刪除最年長的,否則擴容
  • 如果removeEldestEntry返回true,那么刪除老的鍵值對,否則的話如果超過閾值,進行擴容

removeEldestEnrey方法

protected boolean More removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
  • removeEldestEntry方法,也就是是否刪除最年長的Entry。這里始終返回的是false,我們可以對他進行重寫

recordAccess方法

  • 當put的是新的鍵值對,鍵是之前沒有的話,那么會創建一個新的Entry,而如果是之前有的話,那么會覆蓋value並執行revordAccess,中文意思為記錄訪問
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 remove() {
    before.after = after;
    after.before = before;
}
private void addBefore(Entry<K,V> existingEntry) {
    after  = existingEntry;
    before = existingEntry.before;
    before.after = this;
    after.before = this;
}
  • 這個方法會調用remove方法,而這個方法實際上是改變鏈表的指針,執行后,相當於之前沒有這個這個Entry(可以自己動手畫一下指針指向),然后再將這個Entry重新加入,這個時候以前添加的Entry,由於最近被使用,就被移到到鏈表的后面了
  • 同樣使用get的時候也會調用此方法,也就是會由於被訪問被移動到前面

總結

  • 初始化一個LinkedHash,會創建一個header
  • put的時候,將Entry放入數組中后,會和header連在一起
  • 再put的一個的時候,同樣加入數組后,會繼續和前面一個相連,此時header,小黑,小紅三個連在一起,
  • 此時get小黑,小黑會從鏈表中移除,此時相當於只添加了小紅,然后再將小黑添加進去,這樣的話就保證了,最新訪問的在后面
  • 如果put的時候該鍵是之前已有的,那么會覆蓋value,然后重新調整該Entry在鏈表中的位置

LRU實現

  • 所謂LRU也就是Least Recently Used,最近最少使用,當緩存滿了,刪除最少使用的
  • 那么LinkedHashMap適合於解決這個問題,因為他使用了雙向鏈表,將最新訪問的移到了后面,那么這樣的話,前面的就是最少使用,這時候就可以將最少使用的刪除
  • removeEldestEntry,前面說過這方法,這個方法就是刪除最少使用的,默認下不會刪除,因為該方法返回false,所以當空間滿了的話會擴容,我們可以重寫這個方法,那么這樣就不需要擴容,滿了的時候刪除最少使用的就可以
  • 重寫removeEldestEntry,當大小大於容量的時候,會刪除最年長的鍵值對
public class LRUCache {
    private int capacity;
    private Map<Integer, Integer> cache;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new LinkedHashMap<Integer, Integer> (capacity, 0.75f, true) {
            // 定義put后的移除規則,大於容量就刪除eldest
            protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
                return size() > capacity;
            }
        };
    }
    public int get(int key) {
        if (cache.containsKey(key)) {
            return cache.get(key);
        } else
            return -1;
    }
    public void set(int key, int value) {
        cache.put(key, value);
    }
    public static void main(String[] args) {
		LRUCache cache=new LRUCache(2);
		cache.set(1,1);
		cache.set(2, 2);
		//此時超出容量,put3的時候刪除cache
		cache.set(3, 3);
		Set<Map.Entry<Integer,Integer>> set = cache.cache.entrySet();
	    Iterator<Map.Entry<Integer,Integer>> iterator = set.iterator();
	    
	    while (iterator.hasNext())
	    {
	        System.out.print(iterator.next() + "\t");
	    }
	    System.out.println();
		
		//輸出2=2	3=3
		
	}
}

我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)

作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM