1. 程式人生 > >Map總結,看這篇就夠了

Map總結,看這篇就夠了

java map

概要

學完了Map的全部內容,我們再回頭開開Map的框架圖。
技術分享圖片

第1部分 Map概括

(01) Map 是“鍵值對”映射的抽象接口。
(02) AbstractMap 實現了Map中的絕大部分函數接口。它減少了“Map的實現類”的重復編碼。
(03) SortedMap 有序的“鍵值對”映射接口。
(04) NavigableMap 是繼承於SortedMap的,支持導航函數的接口。
(05) HashMap, Hashtable, TreeMap, WeakHashMap這4個類是“鍵值對”映射的實現類。它們各有區別!

  HashMap 是基於“拉鏈法”實現的散列表。一般用於單線程程序中。
  Hashtable 也是基於“拉鏈法”實現的散列表。它一般用於多線程程序中。

  WeakHashMap 也是基於“拉鏈法”實現的散列表,它一般也用於單線程程序中。相比HashMap,WeakHashMap中的鍵是“弱鍵”,當“弱鍵”被GC回收時,它對應的鍵值對也會被從WeakHashMap中刪除;而HashMap中的鍵是強鍵。
  TreeMap 是有序的散列表,它是通過紅黑樹實現的。它一般用於單線程中存儲有序的映射。

第2部分 HashMap和Hashtable異同

第2.1部分 HashMap和Hashtable的相同點

HashMap和Hashtable都是存儲“鍵值對(key-value)”的散列表,而且都是采用拉鏈法實現的。
存儲的思想都是:通過table數組存儲,數組的每一個元素都是一個Entry;而一個Entry就是一個單向鏈表,Entry鏈表中的每一個節點就保存了key-value鍵值對數據。

添加key-value鍵值對:首先,根據key值計算出哈希值,再計算出數組索引(即,該key-value在table中的索引)。然後,根據數組索引找到Entry(即,單向鏈表),再遍歷單向鏈表,將key和鏈表中的每一個節點的key進行對比。若key已經存在Entry鏈表中,則用該value值取代舊的value值;若key不存在Entry鏈表中,則新建一個key-value節點,並將該節點插入Entry鏈表的表頭位置。
刪除key-value鍵值對:刪除鍵值對,相比於“添加鍵值對”來說,簡單很多。首先,還是根據key計算出哈希值,再計算出數組索引(即,該key-value在table中的索引)。然後,根據索引找出Entry(即,單向鏈表)

。若節點key-value存在與鏈表Entry中,則刪除鏈表中的節點即可。

上面介紹了HashMap和Hashtable的相同點。正是由於它們都是散列表,我們關註更多的是“它們的區別,以及它們分別適合在什麽情況下使用”。那接下來,我們先看看它們的區別。

第2.2部分 HashMap和Hashtable的不同點

1 繼承和實現方式不同

HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。
Hashtable 繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable接口。

HashMap的定義:

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable { ... }

Hashtable的定義:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable { ... }

從中,我們可以看出:
1.1 HashMap和Hashtable都實現了Map、Cloneable、java.io.Serializable接口。
實現了Map接口,意味著它們都支持key-value鍵值對操作。支持“添加key-value鍵值對”、“獲取key”、“獲取value”、“獲取map大小”、“清空map”等基本的key-value鍵值對操作。
實現了Cloneable接口,意味著它能被克隆。
實現了java.io.Serializable接口,意味著它們支持序列化,能通過序列化去傳輸。

1.2 HashMap繼承於AbstractMap,而Hashtable繼承於Dictionary
Dictionary是一個抽象類,它直接繼承於Object類,沒有實現任何接口。Dictionary類是JDK 1.0的引入的。雖然Dictionary也支持“添加key-value鍵值對”、“獲取value”、“獲取大小”等基本操作,但它的API函數比Map少;而且 Dictionary一般是通過Enumeration(枚舉類)去遍歷,Map則是通過Iterator(叠代器)去遍歷。 然而‘由於Hashtable也實現了Map接口,所以,它即支持Enumeration遍歷,也支持Iterator遍歷。關於這點,後面還會進一步說明。
AbstractMap是一個抽象類,它實現了Map接口的絕大部分API函數;為Map的具體實現類提供了極大的便利。它是JDK 1.2新增的類。

2 線程安全不同

Hashtable的幾乎所有函數都是同步的,即它是線程安全的,支持多線程。
而HashMap的函數則是非同步的,它不是線程安全的。若要在多線程中使用HashMap,需要我們額外的進行同步處理。 對HashMap的同步處理可以使用Collections類提供的synchronizedMap靜態方法,或者直接使用JDK 5.0之後提供的java.util.concurrent包裏的ConcurrentHashMap類。

3 對null值的處理不同

HashMap的key、value都可以為null。
Hashtable的key、value都不可以為null。

我們先看看HashMap和Hashtable “添加key-value”的方法

HashMap的添加key-value的方法

 1 // 將“key-value”添加到HashMap中
 2 public V put(K key, V value) {
 3     // 若“key為null”,則將該鍵值對添加到table[0]中。
 4     if (key == null)
 5         return putForNullKey(value);
 6     // 若“key不為null”,則計算該key的哈希值,然後將其添加到該哈希值對應的鏈表中。
 7     int hash = hash(key.hashCode());
 8     int i = indexFor(hash, table.length);
 9     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
10         Object k;
11         // 若“該key”對應的鍵值對已經存在,則用新的value取代舊的value。然後退出!
12         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
13             V oldValue = e.value;
14             e.value = value;
15             e.recordAccess(this);
16             return oldValue;
17         }
18     }
19 
20     // 若“該key”對應的鍵值對不存在,則將“key-value”添加到table中
21     modCount++;
22     addEntry(hash, key, value, i);
23     return null;
24 }
25 
26 // putForNullKey()的作用是將“key為null”鍵值對添加到table[0]位置
27 private V putForNullKey(V value) {
28     for (Entry<K,V> e = table[0]; e != null; e = e.next) {
29         if (e.key == null) {
30             V oldValue = e.value;
31             e.value = value;
32             // recordAccess()函數什麽也沒有做
33             e.recordAccess(this);
34             return oldValue;
35         }
36     }
37     // 添加第1個“key為null”的元素都table中的時候,會執行到這裏。
38     // 它的作用是將“設置table[0]的key為null,值為value”。
39     modCount++;
40     addEntry(0, null, value, 0);
41     return null;
42 }

Hashtable的添加key-value的方法

 1 // 將“key-value”添加到Hashtable中
 2 public synchronized V put(K key, V value) {
 3     // Hashtable中不能插入value為null的元素!!!
 4     if (value == null) {
 5         throw new NullPointerException();
 6     }
 7 
 8     // 若“Hashtable中已存在鍵為key的鍵值對”,
 9     // 則用“新的value”替換“舊的value”
10     Entry tab[] = table;
11     // Hashtable中不能插入key為null的元素!!!
12     // 否則,下面的語句會拋出異常!
13     int hash = key.hashCode();
14     int index = (hash & 0x7FFFFFFF) % tab.length;
15     for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
16         if ((e.hash == hash) && e.key.equals(key)) {
17             V old = e.value;
18             e.value = value;
19             return old;
20         }
21     }
22 
23     // 若“Hashtable中不存在鍵為key的鍵值對”,
24     // (01) 將“修改統計數”+1
25     modCount++;
26     // (02) 若“Hashtable實際容量” > “閾值”(閾值=總的容量 * 加載因子)
27     //  則調整Hashtable的大小
28     if (count >= threshold) {
29         // Rehash the table if the threshold is exceeded
30         rehash();
31 
32         tab = table;
33         index = (hash & 0x7FFFFFFF) % tab.length;
34     }
35 
36     // (03) 將“Hashtable中index”位置的Entry(鏈表)保存到e中 Entry<K,V> e = tab[index];
37     // (04) 創建“新的Entry節點”,並將“新的Entry”插入“Hashtable的index位置”,並設置e為“新的Entry”的下一個元素(即“新Entry”為鏈表表頭)。        
38     tab[index] = new Entry<K,V>(hash, key, value, e);
39     // (05) 將“Hashtable的實際容量”+1
40     count++;
41     return null;
42 }

根據上面的代碼,我們可以看出:

Hashtable的key或value,都不能為null!否則,會拋出異常NullPointerException。
HashMap的key、value都可以為null。 當HashMap的key為null時,HashMap會將其固定的插入table[0]位置(即HashMap散列表的第一個位置);而且table[0]處只會容納一個key為null的值,當有多個key為null的值插入的時候,table[0]會保留最後插入的value。

4 支持的遍歷種類不同

HashMap只支持Iterator(叠代器)遍歷。
而Hashtable支持Iterator(叠代器)和Enumeration(枚舉器)兩種方式遍歷。

Enumeration 是JDK 1.0添加的接口,只有hasMoreElements(), nextElement() 兩個API接口,不能通過Enumeration()對元素進行修改 。
而Iterator 是JDK 1.2才添加的接口,支持hasNext(), next(), remove() 三個API接口。HashMap也是JDK 1.2版本才添加的,所以用Iterator取代Enumeration,HashMap只支持Iterator遍歷。

5 通過Iterator叠代器遍歷時,遍歷的順序不同

HashMap是“從前向後”的遍歷數組;再對數組具體某一項對應的鏈表,從表頭開始進行遍歷。
Hashtabl是“從後往前”的遍歷數組;再對數組具體某一項對應的鏈表,從表頭開始進行遍歷。

HashMap和Hashtable都實現Map接口,所以支持獲取它們“key的集合”、“value的集合”、“key-value的集合”,然後通過Iterator對這些集合進行遍歷。
由於“key的集合”、“value的集合”、“key-value的集合”的遍歷原理都是一樣的;下面,我以遍歷“key-value的集合”來進行說明。

HashMap 和Hashtable 遍歷"key-value集合"的方式是:(01) 通過entrySet()獲取“Map.Entry集合”。 (02) 通過iterator()獲取“Map.Entry集合”的叠代器,再進行遍歷。

HashMap的實現方式:先“從前向後”的遍歷數組;對數組具體某一項對應的鏈表,則從表頭開始往後遍歷。

1 // 返回“HashMap的Entry集合”
 2 public Set<Map.Entry<K,V>> entrySet() {
 3     return entrySet0();
 4 }
 5 // 返回“HashMap的Entry集合”,它實際是返回一個EntrySet對象
 6 private Set<Map.Entry<K,V>> entrySet0() {
 7     Set<Map.Entry<K,V>> es = entrySet;
 8     return es != null ? es : (entrySet = new EntrySet());
 9 }
10 // EntrySet對應的集合
11 // EntrySet繼承於AbstractSet,說明該集合中沒有重復的EntrySet。
12 private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
13     ...
14     public Iterator<Map.Entry<K,V>> iterator() {
15         return newEntryIterator();
16     }
17     ...
18 }
19 // 返回一個“entry叠代器”
20 Iterator<Map.Entry<K,V>> newEntryIterator()   {
21     return new EntryIterator();
22 }
23 // Entry的叠代器
24 private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
25     public Map.Entry<K,V> next() {
26         return nextEntry();
27     }
28 }
29 private abstract class HashIterator<E> implements Iterator<E> {
30     // 下一個元素
31     Entry<K,V> next;
32     // expectedModCount用於實現fail-fast機制。
33     int expectedModCount;
34     // 當前索引
35     int index;
36     // 當前元素
37     Entry<K,V> current;
38 
39     HashIterator() {
40         expectedModCount = modCount;
41         if (size > 0) { // advance to first entry
42             Entry[] t = table;
43             // 將next指向table中第一個不為null的元素。
44             // 這裏利用了index的初始值為0,從0開始依次向後遍歷,直到找到不為null的元素就退出循環。
45             while (index < t.length && (next = t[index++]) == null)
46                 ;
47         }
48     }
49 
50     public final boolean hasNext() {
51         return next != null;
52     }
53 
54     // 獲取下一個元素
55     final Entry<K,V> nextEntry() {
56         if (modCount != expectedModCount)
57             throw new ConcurrentModificationException();
58         Entry<K,V> e = next;
59         if (e == null)
60             throw new NoSuchElementException();
61 
62         // 註意!!!
63         // 一個Entry就是一個單向鏈表
64         // 若該Entry的下一個節點不為空,就將next指向下一個節點;
65         // 否則,將next指向下一個鏈表(也是下一個Entry)的不為null的節點。
66         if ((next = e.next) == null) {
67             Entry[] t = table;
68             while (index < t.length && (next = t[index++]) == null)
69                 ;
70         }
71         current = e;
72         return e;
73     }
74 
75     ...
76 }

Hashtable的實現方式:先從“後向往前”的遍歷數組;對數組具體某一項對應的鏈表,則從表頭開始往後遍歷。

 1 public Set<Map.Entry<K,V>> entrySet() {
 2     if (entrySet==null)
 3         entrySet = Collections.synchronizedSet(new EntrySet(), this);
 4     return entrySet;
 5 }
 6 
 7 private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
 8     public Iterator<Map.Entry<K,V>> iterator() {
 9         return getIterator(ENTRIES);
10     }
11     ...
12 }
13 
14 private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
15     // 指向Hashtable的table
16     Entry[] table = Hashtable.this.table;
17     // Hashtable的總的大小
18     int index = table.length;
19     Entry<K,V> entry = null;
20     Entry<K,V> lastReturned = null;
21     int type;
22 
23     // Enumerator是 “叠代器(Iterator)” 還是 “枚舉類(Enumeration)”的標誌
24     // iterator為true,表示它是叠代器;否則,是枚舉類。
25     boolean iterator;
26 
27     // 在將Enumerator當作叠代器使用時會用到,用來實現fail-fast機制。
28     protected int expectedModCount = modCount;
29 
30     Enumerator(int type, boolean iterator) {
31         this.type = type;
32         this.iterator = iterator;
33     }
34 
35     // 從遍歷table的數組的末尾向前查找,直到找到不為null的Entry。
36     public boolean hasMoreElements() {
37         Entry<K,V> e = entry;
38         int i = index;
39         Entry[] t = table;
40         /* Use locals for faster loop iteration */
41         while (e == null && i > 0) {
42             e = t[--i];
43         }
44         entry = e;
45         index = i;
46         return e != null;
47     }
48 
49     // 獲取下一個元素
50     // 註意:從hasMoreElements() 和nextElement() 可以看出“Hashtable的elements()遍歷方式”
51     // 首先,從後向前的遍歷table數組。table數組的每個節點都是一個單向鏈表(Entry)。
52     // 然後,依次向後遍歷單向鏈表Entry。
53     public T nextElement() {
54         Entry<K,V> et = entry;
55         int i = index;
56         Entry[] t = table;
57         /* Use locals for faster loop iteration */
58         while (et == null && i > 0) {
59             et = t[--i];
60         }
61         entry = et;
62         index = i;
63         if (et != null) {
64             Entry<K,V> e = lastReturned = entry;
65             entry = e.next;
66             return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
67         }
68         throw new NoSuchElementException("Hashtable Enumerator");
69     }
70 
71     // 叠代器Iterator的判斷是否存在下一個元素
72     // 實際上,它是調用的hasMoreElements()
73     public boolean hasNext() {
74         return hasMoreElements();
75     }
76 
77     // 叠代器獲取下一個元素
78     // 實際上,它是調用的nextElement()
79     public T next() {
80         if (modCount != expectedModCount)
81             throw new ConcurrentModificationException();
82         return nextElement();
83     }
84 
85     ...
86 
87 }

6 容量的初始值 和 增加方式都不一樣

HashMap默認的容量大小是16;增加容量時,每次將容量變為“原始容量x2”。
Hashtable默認的容量大小是11;增加容量時,每次將容量變為“原始容量x2 + 1”。

HashMap默認的“加載因子”是0.75, 默認的容量大小是16。

1 // 默認的初始容量是16,必須是2的冪。
 2 static final int DEFAULT_INITIAL_CAPACITY = 16;
 3 
 4 // 默認加載因子
 5 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 6 
 7 // 指定“容量大小”的構造函數
 8 public HashMap(int initialCapacity) {
 9     this(initialCapacity, DEFAULT_LOAD_FACTOR);
10 }

*當HashMap的 “實際容量” >= “閾值”時,(閾值 = 總的容量 加載因子),就將HashMap的容量翻倍。**

 1 // 新增Entry。將“key-value”插入指定位置,bucketIndex是位置索引。
 2 void addEntry(int hash, K key, V value, int bucketIndex) {
 3     // 保存“bucketIndex”位置的值到“e”中
 4     Entry<K,V> e = table[bucketIndex];
 5     // 設置“bucketIndex”位置的元素為“新Entry”,
 6     // 設置“e”為“新Entry的下一個節點”
 7     table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
 8     // 若HashMap的實際大小 不小於 “閾值”,則調整HashMap的大小
 9     if (size++ >= threshold)
10         resize(2 * table.length);
11 }

Hashtable默認的“加載因子”是0.75, 默認的容量大小是11。

1 // 默認構造函數。
2 public Hashtable() {
3     // 默認構造函數,指定的容量大小是11;加載因子是0.75
4     this(11, 0.75f);
5 }

當Hashtable的 “實際容量” >= “閾值”時,(閾值 = 總的容量 x 加載因子),就將變為“原始容量x2 + 1”。

 1 // 調整Hashtable的長度,將長度變成原來的(2倍+1)
 2 // (01) 將“舊的Entry數組”賦值給一個臨時變量。
 3 // (02) 創建一個“新的Entry數組”,並賦值給“舊的Entry數組”
 4 // (03) 將“Hashtable”中的全部元素依次添加到“新的Entry數組”中
 5 protected void rehash() {
 6     int oldCapacity = table.length;
 7     Entry[] oldMap = table;
 8 
 9     int newCapacity = oldCapacity * 2 + 1;
10     Entry[] newMap = new Entry[newCapacity];
11 
12     modCount++;
13     threshold = (int)(newCapacity * loadFactor);
14     table = newMap;
15 
16     for (int i = oldCapacity ; i-- > 0 ;) {
17         for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
18             Entry<K,V> e = old;
19             old = old.next;
20 
21             int index = (e.hash & 0x7FFFFFFF) % newCapacity;
22             e.next = newMap[index];
23             newMap[index] = e;
24         }
25     }
26 }

7 添加key-value時的hash值算法不同

HashMap添加元素時,是使用自定義的哈希算法。
Hashtable沒有自定義哈希算法,而直接采用的key的hashCode()。

HashMap添加元素時,是使用自定義的哈希算法。

 1 static int hash(int h) {
 2     h ^= (h >>> 20) ^ (h >>> 12);
 3     return h ^ (h >>> 7) ^ (h >>> 4);
 4 }
 5 
 6 // 將“key-value”添加到HashMap中
 7 public V put(K key, V value) {
 8     // 若“key為null”,則將該鍵值對添加到table[0]中。
 9     if (key == null)
10         return putForNullKey(value);
11     // 若“key不為null”,則計算該key的哈希值,然後將其添加到該哈希值對應的鏈表中。
12     int hash = hash(key.hashCode());
13     int i = indexFor(hash, table.length);
14     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
15         Object k;
16         // 若“該key”對應的鍵值對已經存在,則用新的value取代舊的value。然後退出!
17         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
18             V oldValue = e.value;
19             e.value = value;
20             e.recordAccess(this);
21             return oldValue;
22         }
23     }
24 
25     // 若“該key”對應的鍵值對不存在,則將“key-value”添加到table中
26     modCount++;
27     addEntry(hash, key, value, i);
28     return null;
29 }

Hashtable沒有自定義哈希算法,而直接采用的key的hashCode()。

 1 public synchronized V put(K key, V value) {
 2     // Hashtable中不能插入value為null的元素!!!
 3     if (value == null) {
 4         throw new NullPointerException();
 5     }
 6 
 7     // 若“Hashtable中已存在鍵為key的鍵值對”,
 8     // 則用“新的value”替換“舊的value”
 9     Entry tab[] = table;
10     int hash = key.hashCode();
11     int index = (hash & 0x7FFFFFFF) % tab.length;
12     for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
13         if ((e.hash == hash) && e.key.equals(key)) {
14             V old = e.value;
15             e.value = value;
16             return old;
17             }
18     }
19 
20     // 若“Hashtable中不存在鍵為key的鍵值對”,
21     // (01) 將“修改統計數”+1
22     modCount++;
23     // (02) 若“Hashtable實際容量” > “閾值”(閾值=總的容量 * 加載因子)
24     //  則調整Hashtable的大小
25     if (count >= threshold) {
26         // Rehash the table if the threshold is exceeded
27         rehash();
28 
29         tab = table;
30         index = (hash & 0x7FFFFFFF) % tab.length;
31     }
32 
33     // (03) 將“Hashtable中index”位置的Entry(鏈表)保存到e中
34     Entry<K,V> e = tab[index];
35     // (04) 創建“新的Entry節點”,並將“新的Entry”插入“Hashtable的index位置”,並設置e為“新的Entry”的下一個元素(即“新Entry”為鏈表表頭)。        
36     tab[index] = new Entry<K,V>(hash, key, value, e);
37     // (05) 將“Hashtable的實際容量”+1
38     count++;
39     return null;
40 }

8 部分API不同

Hashtable支持contains(Object value)方法,而且重寫了toString()方法;
而HashMap不支持contains(Object value)方法,沒有重寫toString()方法。

最後,再說說“HashMap和Hashtable”使用的情景。
其實,若了解它們之間的不同之處後,可以很容易的區分根據情況進行取舍。例如:(01) 若在單線程中,我們往往會選擇HashMap;而在多線程中,則會選擇Hashtable。(02),若不能插入null元素,則選擇Hashtable;否則,可以選擇HashMap。
但這個不是絕對的標準。例如,在多線程中,我們可以自己對HashMap進行同步,也可以選擇ConcurrentHashMap。當HashMap和Hashtable都不能滿足自己的需求時,還可以考慮新定義一個類,繼承或重新實現散列表;當然,一般情況下是不需要的了。

第3部分 HashMap和WeakHashMap異同

3.1 HashMap和WeakHashMap的相同點

1 它們都是散列表,存儲的是“鍵值對”映射。
2 它們都繼承於AbstractMap,並且實現Map基礎。
3 它們的構造函數都一樣。
它們都包括4個構造函數,而且函數的參數都一樣。
4 默認的容量大小是16,默認的加載因子是0.75。
5 它們的“鍵”和“值”都允許為null。
6 它們都是“非同步的”。

3.2 HashMap和WeakHashMap的不同點

1 HashMap實現了Cloneable和Serializable接口,而WeakHashMap沒有。
HashMap實現Cloneable,意味著它能通過clone()克隆自己。
HashMap實現Serializable,意味著它支持序列化,能通過序列化去傳輸。

2 HashMap的“鍵”是“強引用(StrongReference)”,而WeakHashMap的鍵是“弱引用(WeakReference)”。
WeakReference的“弱鍵”能實現WeakReference對“鍵值對”的動態回收。當“弱鍵”不再被使用到時,GC會回收它,WeakReference也會將“弱鍵”對應的鍵值對刪除。
這個“弱鍵”實現的動態回收“鍵值對”的原理呢?其實,通過WeakReference(弱引用)和ReferenceQueue(引用隊列)實現的。 首先,我們需要了解WeakHashMap中:
第一,“鍵”是WeakReference,即key是弱鍵。
第二,ReferenceQueue是一個引用隊列,它是和WeakHashMap聯合使用的。當弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。 WeakHashMap中的ReferenceQueue是queue。
第三,WeakHashMap是通過數組實現的,我們假設這個數組是table。

接下來,說說“動態回收”的步驟。

(01) 新建WeakHashMap,將“鍵值對”添加到WeakHashMap中。
將“鍵值對”添加到WeakHashMap中時,添加的鍵都是弱鍵。
實際上,WeakHashMap是通過數組table保存Entry(鍵值對);每一個Entry實際上是一個單向鏈表,即Entry是鍵值對鏈表。
(02) 當某“弱鍵”不再被其它對象引用,並被GC回收時。在GC回收該“弱鍵”時,這個“弱鍵”也同時會被添加到queue隊列中。
例如,當我們在將“弱鍵”key添加到WeakHashMap之後;後來將key設為null。這時,便沒有外部外部對象再引用該了key。
接著,當Java虛擬機的GC回收內存時,會回收key的相關內存;同時,將key添加到queue隊列中。
(03) 當下一次我們需要操作WeakHashMap時,會先同步table和queue。table中保存了全部的鍵值對,而queue中保存被GC回收的“弱鍵”;同步它們,就是刪除table中被GC回收的“弱鍵”對應的鍵值對。
例如,當我們“讀取WeakHashMap中的元素或獲取WeakReference的大小時”,它會先同步table和queue,目的是“刪除table中被GC回收的‘弱鍵’對應的鍵值對”。刪除的方法就是逐個比較“table中元素的‘鍵’和queue中的‘鍵’”,若它們相當,則刪除“table中的該鍵值對”。

3.3 HashMap和WeakHashMap的比較測試程序

 1 import java.util.HashMap;
  2 import java.util.Iterator;
  3 import java.util.Map;
  4 import java.util.WeakHashMap;
  5 import java.util.Date;
  6 import java.lang.ref.WeakReference;
  7 
  8 /**
  9  * @desc HashMap 和 WeakHashMap比較程序
 10  *
 11  * @author skywang
 12  * @email [email protected]
 13  */
 14 public class CompareHashmapAndWeakhashmap {
 15 
 16     public static void main(String[] args) throws Exception {
 17 
 18         // 當“弱鍵”是String時,比較HashMap和WeakHashMap
 19         compareWithString();
 20         // 當“弱鍵”是自定義類型時,比較HashMap和WeakHashMap
 21         compareWithSelfClass();
 22     }
 23 
 24     /**
 25      * 遍歷map,並打印map的大小
 26      */
 27     private static void iteratorAndCountMap(Map map) {
 28         // 遍歷map
 29         for (Iterator iter = map.entrySet().iterator();
 30                 iter.hasNext();  ) {
 31             Map.Entry en = (Map.Entry)iter.next();
 32             System.out.printf("map entry : %s - %s\n ",en.getKey(), en.getValue());
 33         }
 34 
 35         // 打印HashMap的實際大小
 36         System.out.printf(" map size:%s\n\n", map.size());
 37     }
 38 
 39     /**
 40      * 通過String對象測試HashMap和WeakHashMap
 41      */
 42     private static void compareWithString() {
 43         // 新建4個String字符串
 44         String w1 = new String("W1");
 45         String w2 = new String("W2");
 46         String h1 = new String("H1");
 47         String h2 = new String("H2");
 48 
 49         // 新建 WeakHashMap對象,並將w1,w2添加到 WeakHashMap中
 50         Map wmap = new WeakHashMap();
 51         wmap.put(w1, "w1");
 52         wmap.put(w2, "w2");
 53 
 54         // 新建 HashMap對象,並將h1,h2添加到 WeakHashMap中
 55         Map hmap = new HashMap();
 56         hmap.put(h1, "h1");
 57         hmap.put(h2, "h2");
 58 
 59         // 刪除HashMap中的“h1”。
 60         // 結果:刪除“h1”之後,HashMap中只有 h2 !
 61         hmap.remove(h1);
 62 
 63         // 將WeakHashMap中的w1設置null,並執行gc()。系統會回收w1
 64         // 結果:w1是“弱鍵”,被GC回收後,WeakHashMap中w1對應的鍵值對,也會被從WeakHashMap中刪除。
 65         //       w2是“弱鍵”,但它不是null,不會被GC回收;也就不會被從WeakHashMap中刪除。
 66         // 因此,WeakHashMap中只有 w2
 67         // 註意:若去掉“w1=null” 或者“System.gc()”,結果都會不一樣!
 68         w1 = null;
 69         System.gc();
 70 
 71         // 遍歷並打印HashMap的大小
 72         System.out.printf(" -- HashMap --\n");
 73         iteratorAndCountMap(hmap);
 74 
 75         // 遍歷並打印WeakHashMap的大小
 76         System.out.printf(" -- WeakHashMap --\n");
 77         iteratorAndCountMap(wmap);
 78     }
 79 
 80     /**
 81      * 通過自定義類測試HashMap和WeakHashMap
 82      */
 83     private static void compareWithSelfClass() {
 84         // 新建4個自定義對象
 85         Self s1 = new Self(10);
 86         Self s2 = new Self(20);
 87         Self s3 = new Self(30);
 88         Self s4 = new Self(40);
 89         
 90         // 新建 WeakHashMap對象,並將s1,s2添加到 WeakHashMap中
 91         Map wmap = new WeakHashMap();
 92         wmap.put(s1, "s1");
 93         wmap.put(s2, "s2");
 94         
 95         // 新建 HashMap對象,並將s3,s4添加到 WeakHashMap中
 96         Map hmap = new HashMap();
 97         hmap.put(s3, "s3");
 98         hmap.put(s4, "s4");
 99 
100         // 刪除HashMap中的s3。
101         // 結果:刪除s3之後,HashMap中只有 s4 !
102         hmap.remove(s3);
103 
104         // 將WeakHashMap中的s1設置null,並執行gc()。系統會回收w1
105         // 結果:s1是“弱鍵”,被GC回收後,WeakHashMap中s1對應的鍵值對,也會被從WeakHashMap中刪除。
106         //       w2是“弱鍵”,但它不是null,不會被GC回收;也就不會被從WeakHashMap中刪除。
107         // 因此,WeakHashMap中只有 s2
108         // 註意:若去掉“s1=null” 或者“System.gc()”,結果都會不一樣!
109         s1 = null;
110         System.gc();
111 
112         /*
113         // 休眠500ms
114         try {
115             Thread.sleep(500);
116         } catch (InterruptedException e) {
117             e.printStackTrace();
118         }
119         // */
120         
121         // 遍歷並打印HashMap的大小
122         System.out.printf(" -- Self-def HashMap --\n");
123         iteratorAndCountMap(hmap);
124 
125         // 遍歷並打印WeakHashMap的大小
126         System.out.printf(" -- Self-def WeakHashMap --\n");
127         iteratorAndCountMap(wmap);
128     }
129 
130     private static class Self { 
131         int id;
132 
133         public Self(int id) {
134             this.id = id;
135         }
136 
137         // 覆蓋finalize()方法
138         // 在GC回收時會被執行
139         protected void finalize() throws Throwable {
140             super.finalize();
141             System.out.printf("GC Self: id=%d addr=0x%s)\n", id, this);
142         }   
143     }
144 }

運行結果:

 -- HashMap --
map entry : H2 - h2
  map size:1

 -- WeakHashMap --
map entry : W2 - w2
  map size:1

 -- Self-def HashMap --
map entry : CompareHashmapAndWeakhashmap$Self@1ff9dc36 - s4
  map size:1

 -- Self-def WeakHashMap --
GC Self: id=10 addr=0xCompareHashmapAndWeakhashmap$Self@12276af2)
map entry : CompareHashmapAndWeakhashmap$Self@59de3f2d - s2
  map size:1

文章有不當之處,歡迎指正,你也可以關註我的微信公眾號:好好學java,獲取優質學習資源。

Map總結,看這篇就夠了