Java-LinkedHashMap整理




Table of Contents

  1. 概述
  2. 例子
    1. HashMap
    2. LinkedHashMap
  3. 實現
    1. 成員變量
    2. 初始化
    3. 儲存
    4. 讀取
  4. 排序模式
  5. 對比下幾種Map
    1. HashMap
    2. Hashtable
    3. LinkedHashMap
    4. TreeMap
  6. 總結

概述

  • 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
// Callbacks to allow LinkedHashMap post-actions
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) { // move node to last
LinkedHashMap.Entry<K,V> last;
// 如果定義了accessOrder,那麼就保證最近訪問節點放到最後
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) { // possibly remove eldest
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) {
// 調用create方法,將新元素以雙向鏈表的的形式加入到映射中。
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;
// 調用元素的addBrefore方法,將元素加入到哈希、雙向鏈接列表。
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) {
// 調用父類HashMap的getEntry()方法,取得要查找的元素。
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;
// 如果定義了LinkedHashMap的迭代順序爲訪問順序,
// 則刪除以前位置上的元素,並將最新訪問的元素添加到鏈表表頭。
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;
}
/**clear鏈表,設置header爲初始狀態*/
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

  1. 概述
  2. 例子
    1. HashMap
    2. LinkedHashMap
  3. 實現
    1. 成員變量
    2. 初始化
    3. 儲存
    4. 讀取
  4. 排序模式
  5. 對比下幾種Map
    1. HashMap
    2. Hashtable
    3. LinkedHashMap
    4. TreeMap
  6. 總結

概述

  • 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
// Callbacks to allow LinkedHashMap post-actions
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) { // move node to last
LinkedHashMap.Entry<K,V> last;
// 如果定義了accessOrder,那麼就保證最近訪問節點放到最後
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) { // possibly remove eldest
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) {
// 調用create方法,將新元素以雙向鏈表的的形式加入到映射中。
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;
// 調用元素的addBrefore方法,將元素加入到哈希、雙向鏈接列表。
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) {
// 調用父類HashMap的getEntry()方法,取得要查找的元素。
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;
// 如果定義了LinkedHashMap的迭代順序爲訪問順序,
// 則刪除以前位置上的元素,並將最新訪問的元素添加到鏈表表頭。
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;
}
/**clear鏈表,設置header爲初始狀態*/
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 結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章