上一篇文章中,詳細說明了HashMap和HashSet的源碼,從源碼的角度可以看出兩者存在深入的聯絡,推測而來,LinkedHashMap和LinkedHashSet必然也存在著深入的聯絡。經過一下分析你會發現,兩者的聯絡和HashMap和HashSet的聯絡一樣。
廢話不多說,首先LinkedHashMap源碼: LinkedHashMap源碼
/* * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values * * @author Josh Bloch * @see Object#hashCode() * @see Collection * @see Map * @see HashMap * @see TreeMap * @see Hashtable * @since 1.4 * 繼承自HashMap * 主要作用是:能夠儲存存放元素的先後順序 * 當進行元素的遍曆的時候, * 能夠按照存放的先後順序進行遍曆。 * 因此效能弱於HashMap的效能。 * 但在遍曆所有元素時具有較好的效能。 */public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{ private static final long serialVersionUID = 3801124242820219131L; /** * The head of the doubly linked list. */ private transient Entry<K,V> header; /** * The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. * * @serial */ private final boolean accessOrder; /** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance * with the specified initial capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } /** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance * with the specified initial capacity and a default load factor (0.75). * * @param initialCapacity the initial capacity * @throws IllegalArgumentException if the initial capacity is negative */ public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } /** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance * with the default initial capacity (16) and load factor (0.75). */ public LinkedHashMap() { super(); accessOrder = false; } /** * Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with * the same mappings as the specified map. The <tt>LinkedHashMap</tt> * instance is created with a default load factor (0.75) and an initial * capacity sufficient to hold the mappings in the specified map. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ public LinkedHashMap(Map<? extends K, ? extends V> m) { super(m); accessOrder = false; } /** * Constructs an empty <tt>LinkedHashMap</tt> instance with the * specified initial capacity, load factor and ordering mode. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @param accessOrder the ordering mode - <tt>true</tt> for * access-order, <tt>false</tt> for insertion-order * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } /** * Called by superclass constructors and pseudoconstructors (clone, * readObject) before any entries are inserted into the map. Initializes * the chain. * 初始化一個頭結點 * 頭結點的前向節點和後向節點都指向頭結點自己 */ @Override void init() { header = new Entry<>(-1, null, null, null); header.before = header.after = header; } /** * Transfers all entries to new table array. This method is called * by superclass resize. It is overridden for performance, as it is * faster to iterate using our linked list. * 轉移所有的entries元素到新的table數組中。 * 此處的transfer要比HashMap裡面的transfer簡單多了。 * 主要是因為有何header.雙向鏈表。通過header就可以遍曆原來數組 * 中的所有元素。 * 要做的公用就是把原來的數組中的元素拷貝到新數組中去就可以了。 * HashMap中transfer之所以看著麻煩是因為用了兩層迴圈, * 第二層迴圈它需要儲存鏈表中的位置。有三句指派陳述式,讓人覺得繞。 */ @Override void transfer(HashMap.Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e = header.after; e != header; e = e.after) { if (rehash) e.hash = (e.key == null) ? 0 : hash(e.key); int index = indexFor(e.hash, newCapacity); e.next = newTable[index]; newTable[index] = e; } } /** * Returns <tt>true</tt> if this map maps one or more keys to the * specified value. * * @param value value whose presence in this map is to be tested * @return <tt>true</tt> if this map maps one or more keys to the * specified value * 是否包含value值。簡單,一看就明白。 */ public boolean containsValue(Object value) { // Overridden to take advantage of faster iterator if (value==null) { for (Entry e = header.after; e != header; e = e.after) if (e.value==null) return true; } else { for (Entry e = header.after; e != header; e = e.after) if (value.equals(e.value)) return true; } return false; } /** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily</i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases. * get方法,通過key擷取值。 * getEntry(key)使用父類中的方法,有key值得到hash值, * 然後得到數組索引index,遍曆鏈表得到Entry值。 */ 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; } /** * Removes all of the mappings from this map. * The map will be empty after this call returns. * 清除所有元素 * 前向引用、後向引用均指向自己 */ public void clear() { super.clear(); header.before = header.after = header; } /** * LinkedHashMap entry. * 整合父類的內部類 */ private static class Entry<K,V> extends HashMap.Entry<K,V> { // These fields comprise the doubly linked list used for iteration. /** * 前一個節點和後一個節點,用於記錄元素添加的順序,用於元素遍曆 * 與next的區別在於,next是解決衝突的問題而可能形成的鏈表 */ Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } /** * Removes this entry from the linked list. * 刪除當前元素。也就是調用該方法的this * 把前一個元素的後向引用自己的後向 * 把後一個元素的前向引用自己的前向 */ private void remove() { before.after = after; after.before = before; } /** * Inserts this entry before the specified existing entry in the list. * 在參數existingEntry的前面插入元素 * 也就是將調用該方法的this元素插入existingEntry的前面。 * this.after = existingEntry; * this.before = existingEntry.before; * this.before.after = this; * this.after.before = this; * 以上四步賦值操作全部都對this操作。很容易明白。 */ private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; } /** * This method is invoked by the superclass whenever the value * of a pre-existing entry is read by Map.get or modified by Map.set. * If the enclosing Map is access-ordered, it moves the entry * to the end of the list; otherwise, it does nothing. */ void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } void recordRemoval(HashMap<K,V> m) { remove(); } } /** * 迭代器 * 此時的迭代器按照插入順序進行迭代 */ 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; } /** * 刪除上一次遍曆的元素 * lastReturned代表上一次遍曆的資料 * lastReturned在nextEntry()方法中會儲存上一次遍曆的資料 */ public void remove() { if (lastReturned == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); LinkedHashMap.this.remove(lastReturned.key); lastReturned = null; expectedModCount = modCount; } /** * 1、迭代器的關鍵是nextEntry()方法 * 2、迭代的過程中,不允許修改集合中的資料。 * 只能使用remove()方法刪除上一個遍曆的資料。 * 否則modCount與expectedModCount不等,將引發異常。 * 3、遍曆過程中,會使用hasNext()方法判斷是否還有下一個元素。 * 所以正常情況下,不會引發NoSuchElementException異常。 * 4、nextEntry代表下一個元素。 * e代表當前要遍曆的元素。 * lastReturned代表上一次遍曆的元素, * 用於remove方法中刪除上一次遍曆的元素。 * 5、LinkedHashMap能夠按照插入順序進行遍曆的原因, * 就在於使用了before和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; } } private class KeyIterator extends LinkedHashIterator<K> { public K next() { return nextEntry().getKey(); } } private class ValueIterator extends LinkedHashIterator<V> { public V next() { return nextEntry().value; } } private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } } // These Overrides alter the behavior of superclass view iterator() methods /** * 重寫父類的方法 * 父類方法在調用過程中會調用對應的這 * 三個方法 */ Iterator<K> newKeyIterator() { return new KeyIterator(); } Iterator<V> newValueIterator() { return new ValueIterator(); } Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); } /** * This override alters behavior of superclass put method. It causes newly * allocated entry to get inserted at the end of the linked list and * removes the eldest entry if appropriate. * 這裡調用父類的addEntry方法。 * 父類的addEntry方法會resize判斷,然後調用createEntry方法。 * 說白了這裡調用父類的方法進行了resize判斷,然後使用 * 自己的createEntry方法把資料添加進來。 */ void addEntry(int hash, K key, V value, int bucketIndex) { super.addEntry(hash, key, value, bucketIndex); // Remove eldest entry if instructed Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { /** * 這個方法是通過key刪除entry元素。 * 由於if語句中返回false,下面語句不會執行, * 所以removeEntryForKey方法並沒有重寫。 * 如果要執行刪除操作,LinkedHashMap類必須重寫 * 該方法。因為父類的刪除操作並沒有改變本類中 * 前向和後向引用的指向。 */ removeEntryForKey(eldest.key); } } /** * This override differs from addEntry in that it doesn't resize the * table or remove the eldest entry. * 添加新元素 * 得到table數組中的原來bucketIndex位置的資料, * 建立一個新的Entry,並與原來bucketIndex位置的資料形成鏈表, * 然後儲存在table數組中。 * 這幾步與HashMap中的createEntry()方法一致。 * e.addBefore(header);實現把header添加到e的前面。 * */ void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<>(hash, key, value, old); table[bucketIndex] = e; e.addBefore(header); size++; } protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }}
源碼中的英文注釋部分並沒有去掉,方便大家查看對照。
對於LinkedHashMap要說的,也沒什麼特別的地方,理解了HashMap的話,基本理解LinkedHashMap也無壓力了。
1、首先明確LinkedHashMap的功能,就是在HashMap的基礎上實現遍曆元素的時候按照插入順序進行遍曆。為實現這個功能,就在Entry類中定義了前向引用和後向引用。其實完全可以僅僅使用一個後向引用就可以實現的。那麼為什麼採用雙向鏈表呢。
個人理解的原因是:通過使用雙向鏈表,鏈表在元素的插入、刪除等操作時易於維護。如果僅僅有一個後向引用,刪除、插入元素時都要通過header引用遍曆到待刪除的元素的前一個元素,那麼將會是很麻煩的事情。必然降低效能。所以通過雙向鏈表,插入刪除都很方便。
2、雙向鏈表頭引用。header是LinkedHashMap中維護的一個Entry變數,該引用代表第一個插入的元素。遍曆的時候,會從該元素開始進行遍曆。
3、一個好圖,不得不盜過來哈哈~~
很說明問題的一張圖。
4、當有新元素加入Map的時候會調用Entry的addEntry方法,會調用removeEldestEntry方法,這裡就是實現LRU元素到期機制的地方,預設的情況下removeEldestEntry方法只返回false表示元素永遠不到期。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }
返回false表示不到期,不過此方法可以通過繼承該類進行重寫,這樣可以實現定期刪除老舊資料的目的。
此方法通常不以任何方式修改映射,相反允許映射在其傳回值的指引下進行自我修改。如果用此映射構建LRU緩衝,則非常方便,它允許映射通過刪除舊條目來減少記憶體損耗。 LinkedHashSet源碼
/* * @author Josh Bloch * @see Object#hashCode() * @see Collection * @see Set * @see HashSet * @see TreeSet * @see Hashtable * @since 1.4 * 該類同樣實現的功能是: * 遍曆全部元素時可以按照插入順序遍曆。 * 使得遍曆得到的資料順序和插入順序一致。 * 該類非常簡單,就是四個構造器。 * 全部調用父類的同一個構造器完成。 * HashSet(int initialCapacity, float loadFactor, boolean dummy) { * map = new LinkedHashMap<>(initialCapacity, loadFactor); * } * 這就是所調用的父類的構造器。 * 第三個參數dummy是忽略,並無用處。 * 從構造器中可以看出,裡面使用了LinkedHashMap來完成LinkedHashSet * 所要實現的功能。 */public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable { private static final long serialVersionUID = -2851667679971038690L; /** * Constructs a new, empty linked hash set with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity of the linked hash set * @param loadFactor the load factor of the linked hash set * @throws IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive */ public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); } /** * Constructs a new, empty linked hash set with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity of the LinkedHashSet * @throws IllegalArgumentException if the initial capacity is less * than zero */ public LinkedHashSet(int initialCapacity) { super(initialCapacity, .75f, true); } /** * Constructs a new, empty linked hash set with the default initial * capacity (16) and load factor (0.75). */ public LinkedHashSet() { super(16, .75f, true); } /** * Constructs a new linked hash set with the same elements as the * specified collection. The linked hash set is created with an initial * capacity sufficient to hold the elements in the specified collection * and the default load factor (0.75). * * @param c the collection whose elements are to be placed into * this set * @throws NullPointerException if the specified collection is null */ public LinkedHashSet(Collection<? extends E> c) { super(Math.max(2*c.size(), 11), .75f, true); addAll(c); }}
代碼中注釋部分已經說得很明白了,該類非常簡單,只要看明白LinkedHashMap即可。該類只有四個構造器沒有方法。全部複用父類的方法實現。
如果大家有什麼問題或疑問,歡迎批評真正。本人水平有限,多多指教。。【握手~】^_^