容器學習三:LinkedHashMap源碼分析

原文:http://zy19982004.iteye.com/blog/1663303

 

一.LinkedHashMap的存儲結構

LinkedHashMap存儲結構

  1. LinkedHashMap是繼承HashMap,也就繼承了HashMap的結構,也就是圖中的結構2,在下文中我用"Entry數組+next鏈表"來描述。而LinkedHashMap有其自己的變量header,也就是圖中的結構1,下文中我用"header鏈表"來描述。
  2. 結構1中的Entry和結構2中的Entry本是同一個,結構1中應該就只有一個header,它指向的是結構2中的e1 e2,但這樣會使結構圖難畫。爲了說明問題的方便,我把結構2裏的e1 e2在結構1中多畫一個。

 

二.LinkedHashMap成員變量

Java代碼 複製代碼 收藏代碼
  1. // LinkedHashMap維護了一個鏈表,header是鏈表頭。此鏈表不同於HashMap裏面的那個next鏈表  
  2. private transient Entry<K, V> header;  
  3.   
  4. // LRU:Least Recently Used最近最少使用算法  
  5. // accessOrder決定是否使用此算法,accessOrder=true使用  
  6. private final boolean accessOrder;  
	// LinkedHashMap維護了一個鏈表,header是鏈表頭。此鏈表不同於HashMap裏面的那個next鏈表
	private transient Entry<K, V> header;

	// LRU:Least Recently Used最近最少使用算法
	// accessOrder決定是否使用此算法,accessOrder=true使用
	private final boolean accessOrder;

 

三.LinkedHashMap裏的Entry對象

Java代碼 複製代碼 收藏代碼
  1.       // 繼承了HashMap.Entry,其他幾個方法邊用邊分析  
  2. rivate static class Entry<K, V> extends HashMap.Entry<K, V> {  
  3. // 增加了兩個屬性,每個Entry有before Entry和after Entry,就構成了一個鏈表  
  4. Entry<K, V> before, after;  
  5.   
  6. Entry(int hash, K key, V value, HashMap.Entry<K, V> next) {  
  7.     super(hash, key, value, next);  
  8. }  
  9.   
  10. private void addBefore(Entry<K, V> existingEntry) {  
  11.     .....  
  12. }  
  13.   
  14. void recordAccess(HashMap<K, V> m) {  
  15.     .....  
  16. }  
  17.   
  18. void recordRemoval(HashMap<K, V> m) {  
  19.     .....  
  20. }  
  21.   
  22. private void remove() {  
  23.     .....  
  24. }  
        // 繼承了HashMap.Entry,其他幾個方法邊用邊分析
	private static class Entry<K, V> extends HashMap.Entry<K, V> {
		// 增加了兩個屬性,每個Entry有before Entry和after Entry,就構成了一個鏈表
		Entry<K, V> before, after;

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

		private void addBefore(Entry<K, V> existingEntry) {
			.....
		}

		void recordAccess(HashMap<K, V> m) {
			.....
		}
		
		void recordRemoval(HashMap<K, V> m) {
			.....
		}
		
		private void remove() {
			.....
		}
	}
 

四.構造函數

Java代碼 複製代碼 收藏代碼
  1. //默認accessOrder爲false  
  2. //調用HashMap構造函數  
  3. public LinkedHashMap() {  
  4.     super();  
  5.     accessOrder = false;  
  6. }  
  7.   
  8. //如果想實現LRU算法,參考這個構造函數  
  9. public LinkedHashMap(int initialCapacity, float loadFactor,  
  10.         boolean accessOrder) {  
  11.     super(initialCapacity, loadFactor);  
  12.     this.accessOrder = accessOrder;  
  13. }  
  14.   
  15. //模板方法模式,HashMap構造函數裏面的會調用init()方法  
  16. //初始化的時候map裏沒有任何Entry,讓header.before = header.after = header  
  17. void init() {  
  18.     header = new Entry<K, V>(-1nullnullnull);  
  19.     header.before = header.after = header;  
  20. }  
	//默認accessOrder爲false
	//調用HashMap構造函數
	public LinkedHashMap() {
		super();
		accessOrder = false;
	}

	//如果想實現LRU算法,參考這個構造函數
	public LinkedHashMap(int initialCapacity, float loadFactor,
			boolean accessOrder) {
		super(initialCapacity, loadFactor);
		this.accessOrder = accessOrder;
	}
	
	//模板方法模式,HashMap構造函數裏面的會調用init()方法
	//初始化的時候map裏沒有任何Entry,讓header.before = header.after = header
	void init() {
		header = new Entry<K, V>(-1, null, null, null);
		header.before = header.after = header;
	}
 

五.存數據

Java代碼 複製代碼 收藏代碼
  1. //LinkedHashMap沒有put(K key, V value)方法,只重寫了被put調用的addEntry方法  
  2. //1是HashMap裏原有的邏輯,23是LinkedHashMap特有的  
  3. void addEntry(int hash, K key, V value, int bucketIndex) {  
  4.     createEntry(hash, key, value, bucketIndex);  
  5.   
  6.     Entry<K, V> eldest = header.after;  
  7.     //3.如果有必要,移除LRU裏面最老的Entry,否則判斷是否該resize  
  8.     if (removeEldestEntry(eldest)) {  
  9.         removeEntryForKey(eldest.key);  
  10.     } else {  
  11.         if (size >= threshold)  
  12.             resize(2 * table.length);  
  13.     }  
  14. }  
  15.   
  16. void createEntry(int hash, K key, V value, int bucketIndex) {  
  17.     //1.同HashMap一樣:在Entry數組+next鏈表結構裏面加入Entry  
  18.     HashMap.Entry<K, V> old = table[bucketIndex];  
  19.     Entry<K, V> e = new Entry<K, V>(hash, key, value, old);  
  20.     table[bucketIndex] = e;  
  21.     //2.把新Entry也加到header鏈表結構裏面去  
  22.     e.addBefore(header);  
  23.     size++;  
  24. }  
  25.   
  26. //默認是false,我們可以重寫此方法  
  27. protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {  
  28.     return false;  
  29. }  
  30.   
  31. private static class Entry<K, V> extends HashMap.Entry<K, V> {  
  32.     //鏈表插入元素四個步驟,對着圖看  
  33.     private void addBefore(Entry<K, V> existingEntry) {  
  34.         after = existingEntry;                //1  
  35.         before = existingEntry.before;     //2  
  36.         before.after = this;                   //3  
  37.         after.before = this;                   //4  
  38.     }  
  39.        }  
  40.        
  41.         //如果走到resize,會調用這裏重寫的transfer  
  42. //HashMap裏面的transfer是n * m次運算,LinkedHashtable重寫後是n + m次運算  
  43. void transfer(HashMap.Entry[] newTable) {  
  44.     int newCapacity = newTable.length;  
  45.     //直接遍歷header鏈表,HashMap裏面是遍歷Entry數組  
  46.     for (Entry<K, V> e = header.after; e != header; e = e.after) {  
  47.         int index = indexFor(e.hash, newCapacity);  
  48.         e.next = newTable[index];  
  49.         newTable[index] = e;  
  50.     }  
  51.  }  
	//LinkedHashMap沒有put(K key, V value)方法,只重寫了被put調用的addEntry方法
	//1是HashMap裏原有的邏輯,23是LinkedHashMap特有的
	void addEntry(int hash, K key, V value, int bucketIndex) {
		createEntry(hash, key, value, bucketIndex);

		Entry<K, V> eldest = header.after;
		//3.如果有必要,移除LRU裏面最老的Entry,否則判斷是否該resize
		if (removeEldestEntry(eldest)) {
			removeEntryForKey(eldest.key);
		} else {
			if (size >= threshold)
				resize(2 * table.length);
		}
	}
	
	void createEntry(int hash, K key, V value, int bucketIndex) {
		//1.同HashMap一樣:在Entry數組+next鏈表結構裏面加入Entry
		HashMap.Entry<K, V> old = table[bucketIndex];
		Entry<K, V> e = new Entry<K, V>(hash, key, value, old);
		table[bucketIndex] = e;
		//2.把新Entry也加到header鏈表結構裏面去
		e.addBefore(header);
		size++;
	}
	
	//默認是false,我們可以重寫此方法
	protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
		return false;
	}

	private static class Entry<K, V> extends HashMap.Entry<K, V> {
		//鏈表插入元素四個步驟,對着圖看
		private void addBefore(Entry<K, V> existingEntry) {
			after = existingEntry;                //1
			before = existingEntry.before;     //2
			before.after = this;                   //3
			after.before = this;                   //4
		}
        }
      
         //如果走到resize,會調用這裏重寫的transfer
	//HashMap裏面的transfer是n * m次運算,LinkedHashtable重寫後是n + m次運算
	void transfer(HashMap.Entry[] newTable) {
		int newCapacity = newTable.length;
		//直接遍歷header鏈表,HashMap裏面是遍歷Entry數組
		for (Entry<K, V> e = header.after; e != header; e = e.after) {
			int index = indexFor(e.hash, newCapacity);
			e.next = newTable[index];
			newTable[index] = e;
		}
	 }

     下面三個圖是初始化LinkedHashMap------->添加Entry e1------>添加Entry e2時,LinkedHashMap結構的變化。

LinkedHashMap存儲結構

LinkedHashMap存儲結構

LinkedHashMap存儲結構

 

六.取數據

Java代碼 複製代碼 收藏代碼
  1. //重寫了get(Object key)方法  
  2. public V get(Object key) {  
  3.     //1.調用HashMap的getEntry方法得到e  
  4.     Entry<K, V> e = (Entry<K, V>) getEntry(key);  
  5.     if (e == null)  
  6.         return null;  
  7.     //2.LinkedHashMap牛B的地方  
  8.     e.recordAccess(this);  
  9.     return e.value;  
  10. }  
  11.   
  12.        // 繼承了HashMap.Entry  
  13. private static class Entry<K, V> extends HashMap.Entry<K, V> {  
  14.     //1.此方法提供了LRU的實現  
  15.     //2.通過12兩步,把最近使用的當前Entry移到header的before位置,而LinkedHashIterator遍歷的方式是從header.after開始遍歷,先得到最近使用的Entry  
  16.     //3.最近使用是什麼意思:accessOrder爲true時,get(Object key)方法會導致Entry最近使用;put(K key, V value)/putForNullKey(value)只有是覆蓋操作時會導致Entry最近使用。它們都會觸發recordAccess方法從而導致Entry最近使用  
  17.     //4.總結LinkedHashMap迭代方式:accessOrder=false時,迭代出的數據按插入順序;accessOrder=true時,迭代出的數據按LRU順序+插入順序  
  18.     //  HashMap迭代方式:橫向數組 * 豎向next鏈表  
  19.     void recordAccess(HashMap<K, V> m) {  
  20.         LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;  
  21.         //如果使用LRU算法  
  22.         if (lm.accessOrder) {  
  23.             lm.modCount++;  
  24.             //1.從header鏈表裏面移除當前Entry  
  25.             remove();  
  26.             //2.把當前Entry移到header的before位置  
  27.             addBefore(lm.header);  
  28.         }  
  29.     }  
  30.       
  31.     //讓當前Entry從header鏈表消失  
  32.     private void remove() {  
  33.         before.after = after;  
  34.         after.before = before;  
  35.     }  
  36.        }  
	//重寫了get(Object key)方法
	public V get(Object key) {
		//1.調用HashMap的getEntry方法得到e
		Entry<K, V> e = (Entry<K, V>) getEntry(key);
		if (e == null)
			return null;
		//2.LinkedHashMap牛B的地方
		e.recordAccess(this);
		return e.value;
	}
 
        // 繼承了HashMap.Entry
	private static class Entry<K, V> extends HashMap.Entry<K, V> {
		//1.此方法提供了LRU的實現
		//2.通過12兩步,把最近使用的當前Entry移到header的before位置,而LinkedHashIterator遍歷的方式是從header.after開始遍歷,先得到最近使用的Entry
		//3.最近使用是什麼意思:accessOrder爲true時,get(Object key)方法會導致Entry最近使用;put(K key, V value)/putForNullKey(value)只有是覆蓋操作時會導致Entry最近使用。它們都會觸發recordAccess方法從而導致Entry最近使用
		//4.總結LinkedHashMap迭代方式:accessOrder=false時,迭代出的數據按插入順序;accessOrder=true時,迭代出的數據按LRU順序+插入順序
		//  HashMap迭代方式:橫向數組 * 豎向next鏈表
		void recordAccess(HashMap<K, V> m) {
			LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;
			//如果使用LRU算法
			if (lm.accessOrder) {
				lm.modCount++;
				//1.從header鏈表裏面移除當前Entry
				remove();
				//2.把當前Entry移到header的before位置
				addBefore(lm.header);
			}
		}
		
		//讓當前Entry從header鏈表消失
		private void remove() {
			before.after = after;
			after.before = before;
		}
        }
 

七.刪數據

Java代碼 複製代碼 收藏代碼
  1.        // 繼承了HashMap.Entry  
  2. private static class Entry<K, V> extends HashMap.Entry<K, V> {  
  3.     //LinkedHashMap沒有重寫remove(Object key)方法,重寫了被remove調用的recordRemoval方法  
  4.     //這個方法的設計也和精髓,也是模板方法模式  
  5.     //HahsMap remove(Object key)把數據從橫向數組 * 豎向next鏈表裏面移除之後(就已經完成工作了,所以HashMap裏面recordRemoval是空的實現調用了此方法  
  6.     //但在LinkedHashMap裏面,還需要移除header鏈表裏面Entry的after和before關係  
  7.     void recordRemoval(HashMap<K, V> m) {  
  8.         remove();  
  9.     }  
  10.       
  11.     //讓當前Entry從header鏈表消失  
  12.     private void remove() {  
  13.         before.after = after;  
  14.         after.before = before;  
  15.     }  
  16. }  
        // 繼承了HashMap.Entry
	private static class Entry<K, V> extends HashMap.Entry<K, V> {
		//LinkedHashMap沒有重寫remove(Object key)方法,重寫了被remove調用的recordRemoval方法
		//這個方法的設計也和精髓,也是模板方法模式
		//HahsMap remove(Object key)把數據從橫向數組 * 豎向next鏈表裏面移除之後(就已經完成工作了,所以HashMap裏面recordRemoval是空的實現調用了此方法
		//但在LinkedHashMap裏面,還需要移除header鏈表裏面Entry的after和before關係
		void recordRemoval(HashMap<K, V> m) {
			remove();
		}
		
		//讓當前Entry從header鏈表消失
		private void remove() {
			before.after = after;
			after.before = before;
		}
	}
 

八.LinkedHashMap EntrySet遍歷

Java代碼 複製代碼 收藏代碼
  1.        private abstract class LinkedHashIterator<T> implements Iterator<T> {  
  2.     //從header.after開始遍歷  
  3.     Entry<K, V> nextEntry = header.after;  
  4.       
  5.     Entry<K, V> nextEntry() {  
  6.         if (modCount != expectedModCount)  
  7.             throw new ConcurrentModificationException();  
  8.         if (nextEntry == header)  
  9.             throw new NoSuchElementException();  
  10.   
  11.         Entry<K, V> e = lastReturned = nextEntry;  
  12.         nextEntry = e.after;  
  13.         return e;  
  14.     }  
  15. }  
        private abstract class LinkedHashIterator<T> implements Iterator<T> {
		//從header.after開始遍歷
		Entry<K, V> nextEntry = header.after;
		
		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;
		}
	}
  1. 上圖中,遍歷的結果是先e1然後e2。
  2. accessOrder爲true時,get(e1.key)或者put(e1.key, value)一下,則結構1變成e2------e1------header,遍歷的結果就是先e2然後e1。

 

九.總結

  1. LinkedHashMap繼承HashMap,結構2裏數據結構的變化交給HashMap就行了。
  2. 結構1裏數據結構的變化就由LinkedHashMap裏重寫的方法去實現。
  3. 簡言之:LinkedHashMap比HashMap多維護了一個鏈表。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章