容器学习三: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多维护了一个链表。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章