上一篇文章中,詳細說明了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即可。該類只有四個構造器沒有方法。全部複用父類的方法實現。
如果大家有什麼問題或疑問,歡迎批評真正!本人水平有限,多多指教!!【握手~】^_^