原文:http://greemranqq.iteye.com/blog/1931880
LinkedHashMap 源碼介紹
一、介紹:
LinkedHashMap 和hashMap 功能類似,都是維護的鍵值對集合,連遍歷 以及方法都類似,唯一的區別在於
hashMap 裏面的元素是根據hash值來決定存放位置的,是無序的,而LinkedHashMap 維護的是一個按順序存放的雙向鏈表,是有序的。
所謂的雙向鏈表其實是鏈表的一種。鏈表:相當於元素 A->B->C ,也就是我可以通過A 找到B ,從而找到C,可以單向移動。而雙向鏈表:A<->B<->C ,也就是說我可以從B 找到 A 和 C,可以雙向移動。
二、源碼分析:
- public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
我們可以看到 這東西是繼承的HashMap 的,說明擁有hashMap 的功能,那麼我們具體來看看 不同在哪裏呢?
1. 構造函數:
- public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
- super(initialCapacity, loadFactor);
- this.accessOrder = accessOrder;
- }
- public LinkedHashMap(int initialCapacity, float loadFactor) {
- super(initialCapacity, loadFactor);
- accessOrder = false;
- }
- public LinkedHashMap(int initialCapacity) {
- super(initialCapacity);
- accessOrder = false;
- }
- 我們看到3個構造函數,其實都是訪問的super(..),也就是hashMap 的構造,區別就在accessOrder
- 來看看它是什麼?
public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
我們看到3個構造函數,其實都是訪問的super(..),也就是hashMap 的構造,區別就在accessOrder
來看看它是什麼?
- //The iteration ordering method for this linked hash map: <tt>true</tt>
- //for access-order, <tt>false</tt> for insertion-order.
- // 簡單說就是這個用來控制元素的順序,
- // true: 是訪問的順序,也就是誰最先訪問,就排在第一位
- // false:存放順序,就是你put 元素的時候的順序
- private final boolean accessOrder;
//The iteration ordering method for this linked hash map: <tt>true</tt>
//for access-order, <tt>false</tt> for insertion-order.
// 簡單說就是這個用來控制元素的順序,
// true: 是訪問的順序,也就是誰最先訪問,就排在第一位
// false:存放順序,就是你put 元素的時候的順序
private final boolean accessOrder;
這裏稍後再看怎麼用的,我們先來分析核心內部類 Entry 類,這幾乎是所有map 都需要的東西。
- private static class Entry<K,V> extends HashMap.Entry<K,V> {
- // These fields comprise the doubly linked list used for iteration.
- // 這裏我們看到,Entry<K,V> 類裏面多了了兩個屬性,專門來方便我們進行鏈表的前後移動 // 的
- Entry<K,V> before, after;
- // 這裏調用了hashMap 的entry 構造,說明還是用的HashMap 的Entry
- Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
- super(hash, key, value, next);
- }
- // ..剩下的稍後說
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
// 這裏我們看到,Entry<K,V> 類裏面多了了兩個屬性,專門來方便我們進行鏈表的前後移動 // 的
Entry<K,V> before, after;
// 這裏調用了hashMap 的entry 構造,說明還是用的HashMap 的Entry
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
// ..剩下的稍後說
}
- 繼續來看這些屬性怎麼利用起來的。對於集合,我們肯定要關注他的 存放元素 和 取元素的方法啦:
- 當我們找遍整個類的時候發現,沒有找到put 方法- -。但是LinkedHashMap 肯定是可以調用put的,因爲繼承了hashMap,
- 那我們把 hashMap 的put方法先取出來吧
繼續來看這些屬性怎麼利用起來的。對於集合,我們肯定要關注他的 存放元素 和 取元素的方法啦:
當我們找遍整個類的時候發現,沒有找到put 方法- -。但是LinkedHashMap 肯定是可以調用put的,因爲繼承了hashMap,
那我們把 hashMap 的put方法先取出來吧
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value);
- int hash = hash(key.hashCode());
- // 這裏都是調用 hashMap 計算位置什麼的,前面hashMap 已經分析過啦
- int i = indexFor(hash, table.length);
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- // 這裏被重寫了 A
- e.recordAccess(this);
- return oldValue;
- }
- }
- modCount++;
- // 這裏被重寫了 B
- addEntry(hash, key, value, i);
- return null;
- }
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
// 這裏都是調用 hashMap 計算位置什麼的,前面hashMap 已經分析過啦
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
// 這裏被重寫了 A
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 這裏被重寫了 B
addEntry(hash, key, value, i);
return null;
}
從上面看出和hashMap 沒啥區別,關鍵就看重寫的方法啦,特別是e.recordAccess 這個方法 在hashMap 介紹中,不是提到不知道幹什麼的嘛,這裏詳細來看看。
- private static class Entry<K,V> extends HashMap.Entry<K,V> {
- /**
- * 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;
- // 如果參數爲true,也就是根據訪問順序
- if (lm.accessOrder) {
- // 迭代控制的變量
- lm.modCount++;
- remove();
- addBefore(lm.header);
- }
- }
- // 這裏我們發現 before和after 雖然定義了屬性,是哪兒賦值的呢?爲什麼不爲null呢?
- // 請回到 構造函數,我們發現有一個init()方法,以前是沒用的,這裏也進行了重寫,請看 // 後面init 方法
- private void remove() {
- // 先移除當前這個空節點
- before.after = after;
- after.before = before;
- }
- /**
- * Inserts this entry before the specified existing entry in the list.
- */
- private void addBefore(Entry<K,V> existingEntry) {
- // 然後將這個空節點 賦值
- after = existingEntry;
- // 當前節點 ,賦值於標誌位的前節點
- before = existingEntry.before;
- // 然後將複製後的節點的 兩個屬性,繼續賦值爲空,等待另一個節點的插入
- before.after = this;
- after.before = this;
- }
- // 整個
- }
private static class Entry<K,V> extends HashMap.Entry<K,V> {
/**
* 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;
// 如果參數爲true,也就是根據訪問順序
if (lm.accessOrder) {
// 迭代控制的變量
lm.modCount++;
remove();
addBefore(lm.header);
}
}
// 這裏我們發現 before和after 雖然定義了屬性,是哪兒賦值的呢?爲什麼不爲null呢?
// 請回到 構造函數,我們發現有一個init()方法,以前是沒用的,這裏也進行了重寫,請看 // 後面init 方法
private void remove() {
// 先移除當前這個空節點
before.after = after;
after.before = before;
}
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
// 然後將這個空節點 賦值
after = existingEntry;
// 當前節點 ,賦值於標誌位的前節點
before = existingEntry.before;
// 然後將複製後的節點的 兩個屬性,繼續賦值爲空,等待另一個節點的插入
before.after = this;
after.before = this;
}
// 整個
}
init() 方法:
- /**
- * The head of the doubly linked list.
- */
- private transient Entry<K,V> header;
- void init() {
- // 這裏默鏈表 表頭是header,並讓hash值爲-1,其他都爲null,僅僅是作爲一個標誌位
- // 初始化這個節點
- header = new Entry<K,V>(-1, null, null, null);
- // 賦值在這裏,默認這是起點,相當於還沒有其他元素
- header.before = header.after = header;
- }
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
void init() {
// 這裏默鏈表 表頭是header,並讓hash值爲-1,其他都爲null,僅僅是作爲一個標誌位
// 初始化這個節點
header = new Entry<K,V>(-1, null, null, null);
// 賦值在這裏,默認這是起點,相當於還沒有其他元素
header.before = header.after = header;
}
可能上面的文字描述很難理解,我們假設:這個雙鏈表結構相當於 一條一節一節連起來的項鍊,當然每一節肯定有鏈接前before 和 after 這樣的連接位置(屬性),中間纔是寶石(數據)。然後,一般項鍊都有一個明顯的位置,方便你去下來的(header),那裏其實是用來首位相連的,當我們覺得長度不夠,需要添加一個寶石的時候,首先會從那裏打開,也就是執行(remove)方法,然後把你需要的寶石連接在最後的位置(after = existingEntry,before = existingEntry.before;);,從程序上來說 就是把那個header 的位置,原先連接器的先取掉掉,那個位置就從新連接新的元素。
繼續講解put 方法,繼續往下看 還有一個addEntry(hash, key, value, i)方法,也進行了重寫
- void addEntry(int hash, K key, V value, int bucketIndex) {
- // 存放元素
- createEntry(hash, key, value, bucketIndex);
- // Remove eldest entry if instructed, else grow capacity if appropriate
- Entry<K,V> eldest = header.after;
- // 這裏始終返回的是false,也就是說 方便我們擴展,重寫的- -!
- if (removeEldestEntry(eldest)) {
- removeEntryForKey(eldest.key);
- } else {
- if (size >= threshold)
- resize(2 * table.length);
- }
- }
- void createEntry(int hash, K key, V value, int bucketIndex) {
- // 通過key 和長度 計算出的位置,去獲得那個元素,可能爲空
- HashMap.Entry<K,V> old = table[bucketIndex];
- // 然後在這個位置創建一個新元素,並讓next 指向old
- Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
- // 把這個元素放進數組的這個位置
- table[bucketIndex] = e;
- // 然後把header 的after before屬性,和元素節點從新連接起來
- // 元素就在header 之前了,也就是可以保證最先訪問(這裏通過Set 集合遍歷順序是反的- -!)
- e.addBefore(header);
- size++;
- }
void addEntry(int hash, K key, V value, int bucketIndex) {
// 存放元素
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry<K,V> eldest = header.after;
// 這裏始終返回的是false,也就是說 方便我們擴展,重寫的- -!
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
void createEntry(int hash, K key, V value, int bucketIndex) {
// 通過key 和長度 計算出的位置,去獲得那個元素,可能爲空
HashMap.Entry<K,V> old = table[bucketIndex];
// 然後在這個位置創建一個新元素,並讓next 指向old
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
// 把這個元素放進數組的這個位置
table[bucketIndex] = e;
// 然後把header 的after before屬性,和元素節點從新連接起來
// 元素就在header 之前了,也就是可以保證最先訪問(這裏通過Set 集合遍歷順序是反的- -!)
e.addBefore(header);
size++;
}
上面可以看出,linkedHashMap,元素默認是放在鏈表前,也就是根據存放順序放的,而實際情況還是用的hashMap 裏面的table 數組,元素位置是隨機存放的,只是linkedHashMap擴展,加入了屬性,對每個元素存放的位置進行了像鏈表結構那樣的鏈接。那麼當我們設置accessOrder=true 的時候如何才控制根據訪問順序進行排列呢?首先請看get 方法:
- } // 也進行了重寫
- public V get(Object key) {
- // 調用父類的get方法
- Entry<K,V> e = (Entry<K,V>)getEntry(key);
- if (e == null)
- return null;
- // 這裏還是調用的剛纔的方法,把當前元素放在header 之前,也就完成了 根據訪問順序排序
- e.recordAccess(this);
- return e.value;
- }
} // 也進行了重寫
public V get(Object key) {
// 調用父類的get方法
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
// 這裏還是調用的剛纔的方法,把當前元素放在header 之前,也就完成了 根據訪問順序排序
e.recordAccess(this);
return e.value;
}
上面對該集合實現雙鏈表結構的原理 以及代碼大概講述了一下,可以去看看圖例更加清晰,下面繼續看看一些方法。
- 1.keySet(),entrySet(),values方法:
- 這裏還是調用的父類的,但是它重寫了幾個方法:
- // 這是重寫的
- Iterator<K> newKeyIterator() { return new KeyIterator(); }
- Iterator<V> newValueIterator() { return new ValueIterator(); }
- terator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }
- // 這裏返回值方式類似,都是通過 nextEntry()返回不同的內容,但是繼承對象變成了LinkedHashIterator,不是原來的 // HashIterator
- private class KeyIterator extends LinkedHashIterator<K> {
- public K next() { return nextEntry().getKey(); }
- }
- // 看看區別吧
- private abstract class LinkedHashIterator<T> implements Iterator<T> {
- // 後一個節點
- Entry<K,V> nextEntry = header.after;
- // 最後返回的節點
- Entry<K,V> lastReturned = null;
- // 迭代的那個變量控制
- int expectedModCount = modCount;
- // 如果下一個元素師頭元素,說明已經到底呢,沒有其他元素了
- public boolean hasNext() {
- return nextEntry != header;
- }
- // 獲得下一個元素
- Entry<K,V> nextEntry() {
- // 這裏就不明白了,當排序參數設置爲true 是,有lm.modCount ++,這裏進行.next 迭代的時候,老報錯
- // 不明白的意義了
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- // 這裏元素迭代完了, 直接拋異常- -,不懂爲什麼這樣設計
- if (nextEntry == header)
- throw new NoSuchElementException();
- // 返回下一個元素,並且將nextEntry 指向下下一個元素
- Entry<K,V> e = lastReturned = nextEntry;
- nextEntry = e.after;
- return e;
- }
- // 刪除
- public void remove() {
- // 這裏同上
- if (lastReturned == null)
- throw new IllegalStateException();
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- // 這裏看出lastReturned 作用就是記錄遍歷的當前位置,方便刪除
- // 這裏直接調用removeEntryForKey 方法就好了,這個- - 看着不爽,反正又不要返回值
- LinkedHashMap.this.remove(lastReturned.key);
- lastReturned = null;
- expectedModCount = modCount;
- }
- }
1.keySet(),entrySet(),values方法:
這裏還是調用的父類的,但是它重寫了幾個方法:
// 這是重寫的
Iterator<K> newKeyIterator() { return new KeyIterator(); }
Iterator<V> newValueIterator() { return new ValueIterator(); }
terator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }
// 這裏返回值方式類似,都是通過 nextEntry()返回不同的內容,但是繼承對象變成了LinkedHashIterator,不是原來的 // HashIterator
private class KeyIterator extends LinkedHashIterator<K> {
public K next() { return nextEntry().getKey(); }
}
// 看看區別吧
private abstract class LinkedHashIterator<T> implements Iterator<T> {
// 後一個節點
Entry<K,V> nextEntry = header.after;
// 最後返回的節點
Entry<K,V> lastReturned = null;
// 迭代的那個變量控制
int expectedModCount = modCount;
// 如果下一個元素師頭元素,說明已經到底呢,沒有其他元素了
public boolean hasNext() {
return nextEntry != header;
}
// 獲得下一個元素
Entry<K,V> nextEntry() {
// 這裏就不明白了,當排序參數設置爲true 是,有lm.modCount ++,這裏進行.next 迭代的時候,老報錯
// 不明白的意義了
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 這裏元素迭代完了, 直接拋異常- -,不懂爲什麼這樣設計
if (nextEntry == header)
throw new NoSuchElementException();
// 返回下一個元素,並且將nextEntry 指向下下一個元素
Entry<K,V> e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
// 刪除
public void remove() {
// 這裏同上
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 這裏看出lastReturned 作用就是記錄遍歷的當前位置,方便刪除
// 這裏直接調用removeEntryForKey 方法就好了,這個- - 看着不爽,反正又不要返回值
LinkedHashMap.this.remove(lastReturned.key);
lastReturned = null;
expectedModCount = modCount;
}
}
- 2.containsKey() 這裏是訪問hashMap 的方法,這裏就不說了。
- containsValue() 進行了重寫
- public boolean containsValue(Object value) {
- // Overridden to take advantage of faster iterator
- // 這裏僅僅是通過鏈表的兩個屬性進行遍歷,hashMap 是通過table 數組進行遍歷,效果差不 // 多
- 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;
2.containsKey() 這裏是訪問hashMap 的方法,這裏就不說了。
containsValue() 進行了重寫
public boolean containsValue(Object value) {
// Overridden to take advantage of faster iterator
// 這裏僅僅是通過鏈表的兩個屬性進行遍歷,hashMap 是通過table 數組進行遍歷,效果差不 // 多
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;
最近我不能直接在iteye 進行編輯,不知道爲什麼,老是卡死,都是記事本寫了,複製過來,沒圖片和格式,望見諒!
總結:1.linkedhashMap 是繼承於hashMap 也就是擁有了他一切功能
2.他是雙鏈表結構,好處可以存取順序
3.可以進行擴展,用來對那些最近訪問元素的優先獲得權
4.存放效率,如果構造參數設置爲true ,由於要維護鏈表結構,效率比hashMap 低一點,但是默認是放在最後,能直接從header 進行操作,效率其實沒多大影響。
5.get 元素類似。如果構造參數爲true ,需要進行鏈表的操作,效率低於hashMap,否則效率一樣。
6.通過Iterator.keySet().iterator() 這樣迭代,數據3000000 的情況,數據默認一個數字,linkedhashMap 慢幾十毫秒,基本沒影響。如果構造參數爲true ,則linkedHashMap 迭代異常。
- // 我內存不夠,i大了要內存溢出
- public static void test(){
- LinkedHashMap p = new LinkedHashMap(3000000,0.75f,false);
- for(int i =0;i<3000000;i++){
- p.put(i, 2);
- }
- long a = System.currentTimeMillis();
- Iterator it1 = p.keySet().iterator();
- while(it1.hasNext()){
- Object o = it1.next();
- p.get(o);
- }
- long b = System.currentTimeMillis();
- System.out.println(b-a);// 250 毫秒
- }
- public static void test2(){
- Map m = new HashMap(3000000);
- for(int i =0;i<3000000;i++){
- m.put(i, 2);
- }
- long a = System.currentTimeMillis();
- Iterator it1 = m.keySet().iterator();
- while(it1.hasNext()){
- Object o = it1.next();
- m.get(o);
- }
- long b = System.currentTimeMillis();
- System.out.println(b-a);// 210 毫秒
- }