Table of Contents
- 概述
- 例子
- HashMap
- LinkedHashMap
- 實現
- 成員變量
- 初始化
- 儲存
- 讀取
- 排序模式
- 對比下幾種Map
- HashMap
- Hashtable
- LinkedHashMap
- TreeMap
- 總結
概述
-
HashMap 是無序的,HashMap 在 put 的時候是根據 key 的 hashcode 進行 hash 然後放入對應的地方。所以遍歷 HashMap 的順序跟 put 的順序不同
-
JAVA 在 JDK1.4 以後提供了 LinkedHashMap 來幫助我們實現了有序的 HashMap
-
LinkedHashMap 是 HashMap的一個子類,它保留了元素插入的順序,如果需要輸出的順序和輸入時的相同,那麼就選用 LinkedHashMap。
-
LinkedHashMap允許使用 null 值和 null 鍵
-
LinkedHashMap 實現與 HashMap 的不同之處在於,LinkedHashMap 維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。
-
根據鏈表中元素的順序可以分爲:按插入順序的鏈表,和按訪問順序(調用 get 方法)的鏈表。默認是按插入順序排序,如果指定按訪問順序排序,那麼調用get方法後,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。
例子
HashMap
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("apple", "蘋果"); map.put("watermelon", "西瓜"); map.put("banana", "香蕉"); map.put("peach", "桃子"); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); System.out.println(entry.getKey() + "=" + entry.getValue()); } }
|
輸出:
banana=香蕉
apple=蘋果
peach=桃子
watermelon=西瓜
可以發現,遍歷HashMap 是沒有順序的
LinkedHashMap
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public static void main(String[] args) { Map<String, String> map = new LinkedHashMap<String, String>(); map.put("apple", "蘋果"); map.put("watermelon", "西瓜"); map.put("banana", "香蕉"); map.put("peach", "桃子"); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); System.out.println(entry.getKey() + "=" + entry.getValue()); } }
|
輸出:
apple=蘋果
watermelon=西瓜
banana=香蕉
peach=桃子
可以發現,其輸出順序是完成按照插入順序的!也就是我們上面所說的保留了插入的順序
接下來驗證按照訪問順序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public static void main(String[] args) { Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true); map.put("apple", "蘋果"); map.put("watermelon", "西瓜"); map.put("banana", "香蕉"); map.put("peach", "桃子"); map.get("banana"); map.get("apple"); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); System.out.println(entry.getKey() + "=" + entry.getValue()); } }
|
輸出:
watermelon=西瓜
peach=桃子
banana=香蕉
apple=蘋果
可以發現,訪問了banana和apple之後,遍歷,他們排在了後面
再來看一個例子:
1 2 3 4 5 6 7 8 9 10 11 12
|
LinkedHashMap<String, Integer> lmap = new LinkedHashMap<String, Integer>(); lmap.put("語文", 1); lmap.put("數學", 2); lmap.put("英語", 3); lmap.put("歷史", 4); lmap.put("政治", 5); lmap.put("地理", 6); lmap.put("生物", 7); lmap.put("化學", 8); for(Entry<String, Integer> entry : lmap.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); }
|
輸出:
語文: 1
數學: 2
英語: 3
歷史: 4
政治: 5
地理: 6
生物: 7
化學: 8
我們來看看LinkedHashMap的內部結構:
看圖應該很清楚了,LinkedHashMap是Hash表和鏈表的實現,並且依靠着雙向鏈表保證了迭代順序是插入的順序。
實現
對於 LinkedHashMap 而言,它繼承與 HashMap(public class LinkedHashMap extends HashMap implements Map)、底層使用哈希表與雙向鏈表來保存所有元素。其基本操作與父類 HashMap 相似,它通過重寫父類相關的方法,來實現自己的鏈接列表特性。下面我們來分析 LinkedHashMap 的源代碼:
在HashMap中提到了下面的定義:
1 2 3 4
|
void afterNodeAccess(Node<K,V> p) { } void afterNodeInsertion(boolean evict) { } void afterNodeRemoval(Node<K,V> p) { }
|
LinkedHashMap繼承於HashMap,因此也重新實現了這3個函數,顧名思義這三個函數的作用分別是:節點訪問後、節點插入後、節點移除後做一些事情。
afterNodeAccess函數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
void afterNodeAccess(Node<K,V> e) { LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }
|
就是說在進行put之後就算是對節點的訪問了,那麼這個時候就會更新鏈表,把最近訪問的放到最後
afterNodeInsertion函數
1 2 3 4 5 6 7 8
|
void afterNodeInsertion(boolean evict) { LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } }
|
成員變量
LinkedHashMap 採用的 hash 算法和 HashMap 相同,但是它重新定義了數組中保存的元素 Entry,該 Entry 除了保存當前對象的引用外,還保存了其上一個元素 before 和下一個元素 after 的引用,從而在哈希表的基礎上又構成了雙向鏈接列表。看源代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
* The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. * 如果爲true,則按照訪問順序;如果爲false,則按照插入順序。 */ private final boolean accessOrder; * 雙向鏈表的表頭元素。 */ private transient Entry<K,V> header; * LinkedHashMap的Entry元素。 * 繼承HashMap的Entry元素,又保存了其上一個元素before和下一個元素after的引用。 */ private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; …… }
|
LinkedHashMap 中的 Entry 集成於 HashMap 的 Entry,但是其增加了 before 和 after 的引用,指的是上一個元素和下一個元素的引用。
初始化
通過源代碼可以看出,在 LinkedHashMap 的構造方法中,實際調用了父類 HashMap 的相關構造方法來構造一個底層存放的 table 數組,但額外可以增加 accessOrder 這個參數,如果不設置,默認爲 false,代表按照插入順序進行迭代;當然可以顯式設置爲 true,代表以訪問順序進行迭代。如:
1 2 3 4
|
public LinkedHashMap(int initialCapacity, float loadFactor,boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
|
我們已經知道 LinkedHashMap 的 Entry 元素繼承 HashMap 的 Entry,提供了雙向鏈表的功能。在上述 HashMap 的構造器中,最後會調用 init() 方法,進行相關的初始化,這個方法在 HashMap 的實現中並無意義,只是提供給子類實現相關的初始化調用。
但在 LinkedHashMap 重寫了 init() 方法,在調用父類的構造方法完成構造後,進一步實現了對其元素 Entry 的初始化操作。
1 2 3 4 5 6 7 8 9 10
|
* 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; }
|
儲存
LinkedHashMap 並未重寫父類 HashMap 的 put 方法,而是重寫了父類 HashMap 的 put 方法調用的子方法void recordAccess(HashMap m) ,void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的雙向鏈接列表的實現。我們在之前的文章中已經講解了HashMap的put方法,我們在這裏重新貼一下
HashMap 的 put 方法的源代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key); 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; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
|
重寫的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } void addEntry(int hash, K key, V value, int bucketIndex) { createEntry(hash, key, value, bucketIndex); Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { if (size >= threshold) resize(2 * table.length); } } void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; e.addBefore(header); size++; } private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; }
|
讀取
LinkedHashMap 重寫了父類 HashMap 的 get 方法,實際在調用父類 getEntry() 方法取得查找的元素後,再判斷當排序模式 accessOrder 爲 true 時,記錄訪問順序,將最新訪問的元素添加到雙向鏈表的表頭,並從原來的位置刪除。由於的鏈表的增加、刪除操作是常量級的,故並不會帶來性能的損失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
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; } void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } * Removes this entry from the linked list. */ private void remove() { before.after = after; after.before = before; } public void clear() { super.clear(); header.before = header.after = header; }
|
排序模式
LinkedHashMap 定義了排序模式 accessOrder,該屬性爲 boolean 型變量,對於訪問順序,爲 true;對於插入順序,則爲 false。一般情況下,不必指定排序模式,其迭代順序即爲默認爲插入順序。
這些構造方法都會默認指定排序模式爲插入順序。如果你想構造一個 LinkedHashMap,並打算按從近期訪問最少到近期訪問最多的順序(即訪問順序)來保存元素,那麼請使用下面的構造方法構造 LinkedHashMap:public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
該哈希映射的迭代順序就是最後訪問其條目的順序,這種映射很適合構建 LRU 緩存。LinkedHashMap 提供了 removeEldestEntry(Map.Entry eldest) 方法。該方法可以提供在每次添加新條目時移除最舊條目的實現程序,默認返回 false,這樣,此映射的行爲將類似於正常映射,即永遠不能移除最舊的元素。
對比下幾種Map
java爲數據結構中的映射定義了一個接口java.util.Map;它有四個實現類,分別是HashMap Hashtable LinkedHashMap 和TreeMap.
Map主要用於存儲健值對,根據鍵得到值,因此不允許鍵重複(重複了覆蓋了),但允許值重複。
HashMap
Hashmap 是一個最常用的Map,它根據鍵的HashCode值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度,遍歷時,取得數據的順序是完全隨機的。 HashMap最多隻允許一條記錄的鍵爲Null;允許多條記錄的值爲 Null;HashMap不支持線程的同步,即任一時刻可以有多個線程同時寫HashMap;可能會導致數據的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
Hashtable
Hashtable與 HashMap類似,它繼承自Dictionary類,不同的是:它不允許記錄的鍵或者值爲空;它支持線程的同步,即任一時刻只有一個線程能寫Hashtable,因此也導致了 Hashtable在寫入時會比較慢。
LinkedHashMap
LinkedHashMap 是HashMap的一個子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的.也可以在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比 LinkedHashMap慢,因爲LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。
TreeMap
TreeMap實現SortMap接口,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。
總結
其實 LinkedHashMap 幾乎和 HashMap 一樣:從技術上來說,不同的是它定義了一個 Entryheader,這個 header 不是放在 Table 裏,它是額外獨立出來的。LinkedHashMap 通過繼承 hashMap 中的 Entry,並添加兩個屬性 Entry before,after,和 header 結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序。
參考:
Table of Contents
- 概述
- 例子
- HashMap
- LinkedHashMap
- 實現
- 成員變量
- 初始化
- 儲存
- 讀取
- 排序模式
- 對比下幾種Map
- HashMap
- Hashtable
- LinkedHashMap
- TreeMap
- 總結
概述
-
HashMap 是無序的,HashMap 在 put 的時候是根據 key 的 hashcode 進行 hash 然後放入對應的地方。所以遍歷 HashMap 的順序跟 put 的順序不同
-
JAVA 在 JDK1.4 以後提供了 LinkedHashMap 來幫助我們實現了有序的 HashMap
-
LinkedHashMap 是 HashMap的一個子類,它保留了元素插入的順序,如果需要輸出的順序和輸入時的相同,那麼就選用 LinkedHashMap。
-
LinkedHashMap允許使用 null 值和 null 鍵
-
LinkedHashMap 實現與 HashMap 的不同之處在於,LinkedHashMap 維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。
-
根據鏈表中元素的順序可以分爲:按插入順序的鏈表,和按訪問順序(調用 get 方法)的鏈表。默認是按插入順序排序,如果指定按訪問順序排序,那麼調用get方法後,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。
例子
HashMap
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("apple", "蘋果"); map.put("watermelon", "西瓜"); map.put("banana", "香蕉"); map.put("peach", "桃子"); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); System.out.println(entry.getKey() + "=" + entry.getValue()); } }
|
輸出:
banana=香蕉
apple=蘋果
peach=桃子
watermelon=西瓜
可以發現,遍歷HashMap 是沒有順序的
LinkedHashMap
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public static void main(String[] args) { Map<String, String> map = new LinkedHashMap<String, String>(); map.put("apple", "蘋果"); map.put("watermelon", "西瓜"); map.put("banana", "香蕉"); map.put("peach", "桃子"); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); System.out.println(entry.getKey() + "=" + entry.getValue()); } }
|
輸出:
apple=蘋果
watermelon=西瓜
banana=香蕉
peach=桃子
可以發現,其輸出順序是完成按照插入順序的!也就是我們上面所說的保留了插入的順序
接下來驗證按照訪問順序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public static void main(String[] args) { Map<String, String> map = new LinkedHashMap<String, String>(16,0.75f,true); map.put("apple", "蘋果"); map.put("watermelon", "西瓜"); map.put("banana", "香蕉"); map.put("peach", "桃子"); map.get("banana"); map.get("apple"); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); System.out.println(entry.getKey() + "=" + entry.getValue()); } }
|
輸出:
watermelon=西瓜
peach=桃子
banana=香蕉
apple=蘋果
可以發現,訪問了banana和apple之後,遍歷,他們排在了後面
再來看一個例子:
1 2 3 4 5 6 7 8 9 10 11 12
|
LinkedHashMap<String, Integer> lmap = new LinkedHashMap<String, Integer>(); lmap.put("語文", 1); lmap.put("數學", 2); lmap.put("英語", 3); lmap.put("歷史", 4); lmap.put("政治", 5); lmap.put("地理", 6); lmap.put("生物", 7); lmap.put("化學", 8); for(Entry<String, Integer> entry : lmap.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); }
|
輸出:
語文: 1
數學: 2
英語: 3
歷史: 4
政治: 5
地理: 6
生物: 7
化學: 8
我們來看看LinkedHashMap的內部結構:
看圖應該很清楚了,LinkedHashMap是Hash表和鏈表的實現,並且依靠着雙向鏈表保證了迭代順序是插入的順序。
實現
對於 LinkedHashMap 而言,它繼承與 HashMap(public class LinkedHashMap extends HashMap implements Map)、底層使用哈希表與雙向鏈表來保存所有元素。其基本操作與父類 HashMap 相似,它通過重寫父類相關的方法,來實現自己的鏈接列表特性。下面我們來分析 LinkedHashMap 的源代碼:
在HashMap中提到了下面的定義:
1 2 3 4
|
void afterNodeAccess(Node<K,V> p) { } void afterNodeInsertion(boolean evict) { } void afterNodeRemoval(Node<K,V> p) { }
|
LinkedHashMap繼承於HashMap,因此也重新實現了這3個函數,顧名思義這三個函數的作用分別是:節點訪問後、節點插入後、節點移除後做一些事情。
afterNodeAccess函數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
void afterNodeAccess(Node<K,V> e) { LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }
|
就是說在進行put之後就算是對節點的訪問了,那麼這個時候就會更新鏈表,把最近訪問的放到最後
afterNodeInsertion函數
1 2 3 4 5 6 7 8
|
void afterNodeInsertion(boolean evict) { LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } }
|
成員變量
LinkedHashMap 採用的 hash 算法和 HashMap 相同,但是它重新定義了數組中保存的元素 Entry,該 Entry 除了保存當前對象的引用外,還保存了其上一個元素 before 和下一個元素 after 的引用,從而在哈希表的基礎上又構成了雙向鏈接列表。看源代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
* The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. * 如果爲true,則按照訪問順序;如果爲false,則按照插入順序。 */ private final boolean accessOrder; * 雙向鏈表的表頭元素。 */ private transient Entry<K,V> header; * LinkedHashMap的Entry元素。 * 繼承HashMap的Entry元素,又保存了其上一個元素before和下一個元素after的引用。 */ private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; …… }
|
LinkedHashMap 中的 Entry 集成於 HashMap 的 Entry,但是其增加了 before 和 after 的引用,指的是上一個元素和下一個元素的引用。
初始化
通過源代碼可以看出,在 LinkedHashMap 的構造方法中,實際調用了父類 HashMap 的相關構造方法來構造一個底層存放的 table 數組,但額外可以增加 accessOrder 這個參數,如果不設置,默認爲 false,代表按照插入順序進行迭代;當然可以顯式設置爲 true,代表以訪問順序進行迭代。如:
1 2 3 4
|
public LinkedHashMap(int initialCapacity, float loadFactor,boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
|
我們已經知道 LinkedHashMap 的 Entry 元素繼承 HashMap 的 Entry,提供了雙向鏈表的功能。在上述 HashMap 的構造器中,最後會調用 init() 方法,進行相關的初始化,這個方法在 HashMap 的實現中並無意義,只是提供給子類實現相關的初始化調用。
但在 LinkedHashMap 重寫了 init() 方法,在調用父類的構造方法完成構造後,進一步實現了對其元素 Entry 的初始化操作。
1 2 3 4 5 6 7 8 9 10
|
* 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; }
|
儲存
LinkedHashMap 並未重寫父類 HashMap 的 put 方法,而是重寫了父類 HashMap 的 put 方法調用的子方法void recordAccess(HashMap m) ,void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的雙向鏈接列表的實現。我們在之前的文章中已經講解了HashMap的put方法,我們在這裏重新貼一下
HashMap 的 put 方法的源代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key); 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; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
|
重寫的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } void addEntry(int hash, K key, V value, int bucketIndex) { createEntry(hash, key, value, bucketIndex); Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { if (size >= threshold) resize(2 * table.length); } } void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; e.addBefore(header); size++; } private void addBefore(Entry<K,V> existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; }
|
讀取
LinkedHashMap 重寫了父類 HashMap 的 get 方法,實際在調用父類 getEntry() 方法取得查找的元素後,再判斷當排序模式 accessOrder 爲 true 時,記錄訪問順序,將最新訪問的元素添加到雙向鏈表的表頭,並從原來的位置刪除。由於的鏈表的增加、刪除操作是常量級的,故並不會帶來性能的損失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
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; } void recordAccess(HashMap<K,V> m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } * Removes this entry from the linked list. */ private void remove() { before.after = after; after.before = before; } public void clear() { super.clear(); header.before = header.after = header; }
|
排序模式
LinkedHashMap 定義了排序模式 accessOrder,該屬性爲 boolean 型變量,對於訪問順序,爲 true;對於插入順序,則爲 false。一般情況下,不必指定排序模式,其迭代順序即爲默認爲插入順序。
這些構造方法都會默認指定排序模式爲插入順序。如果你想構造一個 LinkedHashMap,並打算按從近期訪問最少到近期訪問最多的順序(即訪問順序)來保存元素,那麼請使用下面的構造方法構造 LinkedHashMap:public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
該哈希映射的迭代順序就是最後訪問其條目的順序,這種映射很適合構建 LRU 緩存。LinkedHashMap 提供了 removeEldestEntry(Map.Entry eldest) 方法。該方法可以提供在每次添加新條目時移除最舊條目的實現程序,默認返回 false,這樣,此映射的行爲將類似於正常映射,即永遠不能移除最舊的元素。
對比下幾種Map
java爲數據結構中的映射定義了一個接口java.util.Map;它有四個實現類,分別是HashMap Hashtable LinkedHashMap 和TreeMap.
Map主要用於存儲健值對,根據鍵得到值,因此不允許鍵重複(重複了覆蓋了),但允許值重複。
HashMap
Hashmap 是一個最常用的Map,它根據鍵的HashCode值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度,遍歷時,取得數據的順序是完全隨機的。 HashMap最多隻允許一條記錄的鍵爲Null;允許多條記錄的值爲 Null;HashMap不支持線程的同步,即任一時刻可以有多個線程同時寫HashMap;可能會導致數據的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
Hashtable
Hashtable與 HashMap類似,它繼承自Dictionary類,不同的是:它不允許記錄的鍵或者值爲空;它支持線程的同步,即任一時刻只有一個線程能寫Hashtable,因此也導致了 Hashtable在寫入時會比較慢。
LinkedHashMap
LinkedHashMap 是HashMap的一個子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的.也可以在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比 LinkedHashMap慢,因爲LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。
TreeMap
TreeMap實現SortMap接口,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。
總結
其實 LinkedHashMap 幾乎和 HashMap 一樣:從技術上來說,不同的是它定義了一個 Entryheader,這個 header 不是放在 Table 裏,它是額外獨立出來的。LinkedHashMap 通過繼承 hashMap 中的 Entry,並添加兩個屬性 Entry before,after,和 header 結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序。
參考: