Java集合系列六LinkedHashMap解析
Posted wlrhnh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合系列六LinkedHashMap解析相关的知识,希望对你有一定的参考价值。
2017-08-14 16:30:10
1、简介
LinkedHashMap继承自HashMap,能保证迭代顺序,支持其他Map可选的操作。采用双向链表存储元素,默认的迭代序是插入序。重复插入一个已经存在的key不影响此顺序。如果accessOrder参数被使用且置为true,迭代序使用访问序,访问序受put、get、putAll等方法的影响,但不受集合视图操作的影响(其实HashMap中好像并没有什么视图操作,不像List有subList方法)。LinkedHashMap不是线程安全的。
2、与HashMap相比特殊点
之前提到了,HashMap不保持顺序,但是LinkedHashMap能保证迭代序。同时还支持两种遍历顺序:插入序和访问序。之所以能实现这些功能和效果,是因为LinkedHashMap重载了LinkedEntry,实现了双向链表,所以插入时,同时插入到table数组和双向链表header,如果是访问序,则在get、putAll等方法操作时,会将操作过的元素链接到表尾,保证链表尾部永远是最近使用过元素。
总结就是:LinkedHashMap有两套元素存储机制:数组table和header,所有区别于HashMap的特点都是通过双向链表header实现的。
3、LinkedEntry
LinkedEntry继承自HashMapEntry,新增了2个元素:nxt和prv来实现双向链表,代码如下:
1 /** 2 * LinkedEntry adds nxt/prv double-links to plain HashMapEntry. 3 */ 4 static class LinkedEntry<K, V> extends HashMapEntry<K, V> { 5 LinkedEntry<K, V> nxt; 6 LinkedEntry<K, V> prv; 7 8 /** Create the header entry */ 9 LinkedEntry() { 10 super(null, null, 0, null); 11 nxt = prv = this; 12 } 13 14 /** Create a normal entry */ 15 LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next, 16 LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) { 17 super(key, value, hash, next); 18 this.nxt = nxt; 19 this.prv = prv; 20 } 21 }
4、put操作
LinkedHashMap没有重写put方法,而是重写了put方法调用的addNewEntry方法,该方法执行真正插入一个元素的操作。插入元素时,同时插入到table数组和header双向链表,代码如下:
1 @Override void addNewEntry(K key, V value, int hash, int index) { 2 LinkedEntry<K, V> header = this.header; 3 4 // 移除最久没使用过的元素,removeEldestEntry方法默认返回false,适合子类重写 5 LinkedEntry<K, V> eldest = header.nxt; 6 if (eldest != header && removeEldestEntry(eldest)) { 7 remove(eldest.key); 8 } 9 10 // Create new entry, link it on to list, and put it into table 11 // 1、将元素查到链表尾部; 12 // 2、将元素插入到table数组中; 13 LinkedEntry<K, V> oldTail = header.prv; 14 LinkedEntry<K, V> newTail = new LinkedEntry<K,V>( 15 key, value, hash, table[index], header, oldTail);//newTail.prv = oldTail, newTail.nxt = header,其实就是在Tail元素和Header元素之间插入 16 table[index] = oldTail.nxt = header.prv = newTail; //1、前一行代码只处理了部分双向链表插入操作,这里继续处理,oldTail.nxt = newTail, header.prv = newTail; 17 //2、插入table[index]; 18 } 19 20 @Override void addNewEntryForNullKey(V value) { 21 LinkedEntry<K, V> header = this.header; 22 23 // 移除最久没使用过的元素,removeEldestEntry方法默认返回false,适合子类重写 24 LinkedEntry<K, V> eldest = header.nxt; 25 if (eldest != header && removeEldestEntry(eldest)) { 26 remove(eldest.key); 27 } 28 29 //与addNewEntry方法类似,只是没有插入数组的操作 30 LinkedEntry<K, V> oldTail = header.prv; 31 LinkedEntry<K, V> newTail = new LinkedEntry<K,V>( 32 null, value, 0, null, header, oldTail); 33 entryForNullKey = oldTail.nxt = header.prv = newTail; 34 }
5、get操作
1 /** 2 * Relinks the given entry to the tail of the list. Under access ordering, 3 * this method is invoked whenever the value of a pre-existing entry is 4 * read by Map.get or modified by Map.put. 5 */ 6 private void makeTail(LinkedEntry<K, V> e) { 7 // 将元素e从当前位置移除 8 // Unlink e 9 e.prv.nxt = e.nxt; 10 e.nxt.prv = e.prv; 11 12 // 连接到链表尾部 13 // Relink e as tail 14 LinkedEntry<K, V> header = this.header; 15 LinkedEntry<K, V> oldTail = header.prv; 16 e.nxt = header; 17 e.prv = oldTail; 18 oldTail.nxt = header.prv = e; 19 modCount++; 20 } 21 22 @Override public V get(Object key) { 23 /* 24 * This method is overridden to eliminate the need for a polymorphic 25 * invocation in superclass at the expense of code duplication. 26 */ 27 if (key == null) { 28 HashMapEntry<K, V> e = entryForNullKey; 29 if (e == null) 30 return null; 31 if (accessOrder) //如果是访问序,将当前元素移到链表尾部(保证最近使用的元素在尾部) 32 makeTail((LinkedEntry<K, V>) e); 33 return e.value; 34 } 35 36 // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590). 37 // 这里的遍历操作与HashMap的类似,唯一的区别是:如果是访问序,则将该元素移到链表尾部 38 int hash = secondaryHash(key); 39 HashMapEntry<K, V>[] tab = table; 40 for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; 41 e != null; e = e.next) { 42 K eKey = e.key; 43 if (eKey == key || (e.hash == hash && key.equals(eKey))) { 44 if (accessOrder) 45 makeTail((LinkedEntry<K, V>) e); 46 return e.value; 47 } 48 } 49 return null; 50 }
6、preModify操作
在HashMap的put、putValueForNullKey方法用到了preModify方法,作用就是保证访问序,代码如下:
1 @Override void preModify(HashMapEntry<K, V> e) { 2 if (accessOrder) { 3 makeTail((LinkedEntry<K, V>) e); 4 } 5 }
7、remove操作
LinkedHashMap方法没有重写remove方法,但是重写了postRemove方法,该方法在HashMap的remove方法中有调用,如下:
1 //作用:从双向链表中移除元素e 2 @Override void postRemove(HashMapEntry<K, V> e) { 3 LinkedEntry<K, V> le = (LinkedEntry<K, V>) e; 4 le.prv.nxt = le.nxt; 5 le.nxt.prv = le.prv; 6 le.nxt = le.prv = null; // Help the GC (for performance) 7 }
8、Iterator体系
LinkedHashMap保证迭代顺序,支持插入序和访问序,那么它的Iterator是怎么实现的?体系与HashMap类似,实现了最顶层的LinkedHashIterator,如下:
1 private abstract class LinkedHashIterator<T> implements Iterator<T> { 2 // 1、前面提到过,header只是一个虚拟元素,真正的表头元素是header.nxt,表尾元素是header.prv,所以遍历的时候从表头开始; 3 // 2、nextEntry很简单,直接取nxt即可; 4 LinkedEntry<K, V> next = header.nxt; 5 LinkedEntry<K, V> lastReturned = null; 6 int expectedModCount = modCount; 7 8 public final boolean hasNext() { 9 return next != header; 10 } 11 12 final LinkedEntry<K, V> nextEntry() { 13 if (modCount != expectedModCount) 14 throw new ConcurrentModificationException(); 15 LinkedEntry<K, V> e = next; 16 if (e == header) 17 throw new NoSuchElementException(); 18 next = e.nxt; 19 return lastReturned = e; 20 } 21 22 // remove操作使用HashMap的remove方法,LinkedHashMap重写了postRemove方法 23 public final void remove() { 24 if (modCount != expectedModCount) 25 throw new ConcurrentModificationException(); 26 if (lastReturned == null) 27 throw new IllegalStateException(); 28 LinkedHashMap.this.remove(lastReturned.key); 29 lastReturned = null; 30 expectedModCount = modCount; 31 } 32 }
至于其他的,就不用细说了,和HashMap类似。
以上是关于Java集合系列六LinkedHashMap解析的主要内容,如果未能解决你的问题,请参考以下文章
Java集合-09LinkedHashMap源码解析及使用实例