在Java集合類中最常用的除了ArrayList外,就是HashMap了。本文盡自己所能,盡量詳細的解釋HashMap的源碼。一山還有一山高,有不足之處請之處,定感謝指定並及時修正。
在看HashMap源碼之前先復習一下數據結構。
Java最基本的數據結構有數組和鏈表。數組的特點是空間連續(大小固定)、尋址迅速,但是插入和刪除時需要移動元素,所以查詢快,增加刪除慢。鏈表恰好相反,可動態增加或減少空間以適應新增和刪除元素,但查找時只能順着一個個節點查找,所以增加刪除快,查找慢。有沒有一種結構綜合了數組和鏈表的優點呢?當然有,那就是哈希表(雖說是綜合優點,但實際上查找肯定沒有數組快,插入刪除沒有鏈表快,一種折中的方式吧)。一般采用拉鏈法實現哈希表。哈希表?拉鏈法?可能一下想不起來,不過放張圖就了然了。
(圖片google來的,好多都用了文章用了這張圖了,不知道出處了就沒申明作者了)
學計算機的肯定學過這玩意兒,也不需要解釋,都懂的。
鋪墊了這么多,又是數組又是鏈表,還有哈希表,拉鏈法,該入正題了,我們什么時候用到了這些內容,具體它是怎么實現的?
其實我們一直在用(別告訴我你沒用過HashMap什么的),可能你一直沒去深究,沒看到它如何實現的,所以一直沒感受到。這里主要分析HashMap的源碼,就不再多扯其他的了。
HashMap繼承自AbstractMap,實現了Map接口(這些內容可以參考《Java集合類》)。來看類的定義。
1 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
Map接口定義了所有Map子類必須實現的方法。Map接口中還定義了一個內部接口Entry(為什么要弄成內部接口?改天還要學習學習)。Entry將在后面有詳細的介紹。
AbstractMap也實現了Map接口,並且提供了兩個實現Entry的內部類:SimpleEntry和SimpleImmutableEntry。
定義了接口,接口中又有內部接口,然后有搞了個抽象類實現接口,抽象類里面又搞了兩個內部類實現接口的內部接口,有沒有點繞,為什么搞成這樣呢?先不管了,先看HashMap吧。
HashMap中定義的屬性(應該都能看明白,不過還是解釋一下):
1 /** 2 * 默認的初始容量,必須是2的冪。 3 */ 4 static final int DEFAULT_INITIAL_CAPACITY = 16; 5 /** 6 * 最大容量(必須是2的冪且小於2的30次方,傳入容量過大將被這個值替換) 7 */ 8 static final int MAXIMUM_CAPACITY = 1 << 30; 9 /** 10 * 默認裝載因子,這個后面會做解釋 11 */ 12 static final float DEFAULT_LOAD_FACTOR = 0.75f; 13 /** 14 * 存儲數據的Entry數組,長度是2的冪。看到數組的內容了,接着看數組中存的內容就明白為什么博文開頭先復習數據結構了 15 */ 16 transient Entry[] table; 17 /** 18 * map中保存的鍵值對的數量 19 */ 20 transient int size; 21 /** 22 * 需要調整大小的極限值(容量*裝載因子) 23 */ 24 int threshold; 25 /** 26 *裝載因子 27 */ 28 final float loadFactor; 29 /** 30 * map結構被改變的次數 31 */ 32 transient volatile int modCount;
接着是HashMap的構造方法。
/** *使用默認的容量及裝載因子構造一個空的HashMap */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//計算下次需要調整大小的極限值 table = new Entry[DEFAULT_INITIAL_CAPACITY];//根據默認容量(16)初始化table init(); } /** * 根據給定的初始容量的裝載因子創建一個空的HashMap * 初始容量小於0或裝載因子小於等於0將報異常 */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY)//調整最大容量 initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); int capacity = 1; //設置capacity為大於initialCapacity且是2的冪的最小值 while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); } /** *根據指定容量創建一個空的HashMap */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);//調用上面的構造方法,容量為指定的容量,裝載因子是默認值 } /** *通過傳入的map創建一個HashMap,容量為默認容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的較大者,裝載因子為默認值 */ public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); putAllForCreate(m); }
上面的構造方法中調用到了init()方法,最后一個方法還調用了putAllForCreate(Map<? extends K, ? extends V> m)。init方法是一個空方法,里面沒有任何內容。putAllForCreate看方法名就是創建的時候將傳入的map全部放入新創建的對象中。該方法中還涉及到其他方法,將在后面介紹。
先看初始化table時均使用了Entry,這是HashMap的一個內部類,實現了Map接口的內部接口Entry。
下面給出Map.Entry接口及HashMap.Entry類的內容。
Map.Entry接口定義的方法
1 K getKey();//獲取Key 2 V getValue();//獲取Value 3 V setValue();//設置Value,至於具體返回什么要看具體實現 4 boolean equals(Object o);//定義equals方法用於判斷兩個Entry是否相同 5 int hashCode();//定義獲取hashCode的方法
HashMap.Entry類的具體實現
1 static class Entry<K,V> implements Map.Entry<K,V> { 2 final K key; 3 V value; 4 Entry<K,V> next;//對下一個節點的引用(看到鏈表的內容,結合定義的Entry數組,是不是想到了哈希表的拉鏈法實現?!) 5 final int hash;//哈希值 6 7 Entry(int h, K k, V v, Entry<K,V> n) { 8 value = v; 9 next = n; 10 key = k; 11 hash = h; 12 } 13 14 public final K getKey() { 15 return key; 16 } 17 18 public final V getValue() { 19 return value; 20 } 21 22 public final V setValue(V newValue) { 23 V oldValue = value; 24 value = newValue; 25 return oldValue;//返回的是之前的Value 26 } 27 28 public final boolean equals(Object o) { 29 if (!(o instanceof Map.Entry))//先判斷類型是否一致 30 return false; 31 Map.Entry e = (Map.Entry)o; 32 Object k1 = getKey(); 33 Object k2 = e.getKey(); 34 // Key相等且Value相等則兩個Entry相等 35 if (k1 == k2 || (k1 != null && k1.equals(k2))) { 36 Object v1 = getValue(); 37 Object v2 = e.getValue(); 38 if (v1 == v2 || (v1 != null && v1.equals(v2))) 39 return true; 40 } 41 return false; 42 } 43 // hashCode是Key的hashCode和Value的hashCode的異或的結果 44 public final int hashCode() { 45 return (key==null ? 0 : key.hashCode()) ^ 46 (value==null ? 0 : value.hashCode()); 47 } 48 // 重寫toString方法,是輸出更清晰 49 public final String toString() { 50 return getKey() + "=" + getValue(); 51 } 52 53 /** 54 *當調用put(k,v)方法存入鍵值對時,如果k已經存在,則該方法被調用(為什么沒有內容?) 55 */ 56 void recordAccess(HashMap<K,V> m) { 57 } 58 59 /** 60 * 當Entry被從HashMap中移除時被調用(為什么沒有內容?) 61 */ 62 void recordRemoval(HashMap<K,V> m) { 63 } 64 }
看完屬性和構造方法,接着看HashMap中的其他方法,一個個分析,從最常用的put和get說起吧。
put()
1 public V put(K key, V value) { 2 if (key == null) 3 return putForNullKey(value); 4 int hash = hash(key.hashCode()); 5 int i = indexFor(hash, table.length); 6 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 7 Object k; 8 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 9 V oldValue = e.value; 10 e.value = value; 11 e.recordAccess(this); 12 return oldValue; 13 } 14 } 15 16 modCount++; 17 addEntry(hash, key, value, i); 18 return null; 19 }
當存入的key是null的時候將調用putForNUllKey方法,暫時將這段邏輯放一邊,看key不為null的情況。先調用了hash(int h)方法獲取了一個hash值。
1 static int hash(int h) { 2 // This function ensures that hashCodes that differ only by 3 // constant multiples at each bit position have a bounded 4 // number of collisions (approximately 8 at default load factor). 5 h ^= (h >>> 20) ^ (h >>> 12); 6 return h ^ (h >>> 7) ^ (h >>> 4); 7 }
這個方法的主要作用是防止質量較差的哈希函數帶來過多的沖突(碰撞)問題。Java中int值占4個字節,即32位。根據這32位值進行移位、異或運算得到一個值。
1 static int indexFor(int h, int length) { 2 return h & (length-1); 3 }
indexFor返回hash值和table數組長度減1的與運算結果。為什么使用的是length-1?應為這樣可以保證結果的最大值是length-1,不會產生數組越界問題。
獲取索引位置之后做了什么?探測table[i]所在的鏈表,所發現key值與傳入的key值相同的對象,則替換並返回oldValue。若找不到,則通過addEntry(hash,key,value,i)添加新的對象。來看addEntry(hash,key,value,i)方法。
1 void addEntry(int hash, K key, V value, int bucketIndex) { 2 Entry<K,V> e = table[bucketIndex]; 3 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 4 if (size++ >= threshold) 5 resize(2 * table.length); 6 }
這就是在一個鏈表頭部插入一個節點的過程。獲取table[i]的對象e,將table[i]的對象修改為新增對象,讓新增對象的next指向e。之后判斷size是否到達了需要擴充table數組容量的界限並讓size自增1,如果達到了則調用resize(int capacity)方法將數組容量拓展為原來的兩倍。
1 void resize(int newCapacity) { 2 Entry[] oldTable = table; 3 int oldCapacity = oldTable.length; 4 // 這個if塊表明,如果容量已經到達允許的最大值,即MAXIMUN_CAPACITY,則不再拓展容量,而將裝載拓展的界限值設為計算機允許的最大值。 5 // 不會再觸發resize方法,而是不斷的向map中添加內容,即table數組中的鏈表可以不斷變長,但數組長度不再改變 6 if (oldCapacity == MAXIMUM_CAPACITY) { 7 threshold = Integer.MAX_VALUE; 8 return; 9 } 10 // 創建新數組,容量為指定的容量 11 Entry[] newTable = new Entry[newCapacity]; 12 transfer(newTable); 13 table = newTable; 14 // 設置下一次需要調整數組大小的界限 15 threshold = (int)(newCapacity * loadFactor); 16 }
結合上面給出的注釋,調整數組容量的內容僅剩下將原table中的內容復制到newTable中並將newTable返回給原table。即上面代碼中的“transfer(newTable);table = newTable;”。來看transfer(Entry[] newTable)方法。
1 void transfer(Entry[] newTable) { 2 // 保留原數組的引用到src中, 3 Entry[] src = table; 4 // 新容量使新數組的長度 5 int newCapacity = newTable.length; 6 // 遍歷原數組 7 for (int j = 0; j < src.length; j++) { 8 // 獲取元素e 9 Entry<K,V> e = src[j]; 10 if (e != null) { 11 // 將原數組中的元素置為null 12 src[j] = null; 13 // 遍歷原數組中j位置指向的鏈表 14 do { 15 Entry<K,V> next = e.next; 16 // 根據新的容量計算e在新數組中的位置 17 int i = indexFor(e.hash, newCapacity); 18 // 將e插入到newTable[i]指向的鏈表的頭部 19 e.next = newTable[i]; 20 newTable[i] = e; 21 e = next; 22 } while (e != null); 23 } 24 } 25 }
從上面的代碼可以看出,HashMap之所以不能保持元素的順序有以下幾點原因:第一,插入元素的時候對元素進行哈希處理,不同元素分配到table的不同位置;第二,容量拓展的時候又進行了hash處理;第三,復制原表內容的時候鏈表被倒置。
一個put方法帶出了這么多內容,接着看看putAll吧。
1 public void putAll(Map<? extends K, ? extends V> m) { 2 int numKeysToBeAdded = m.size(); 3 if (numKeysToBeAdded == 0) 4 return; 5 // 為什么判斷條件是numKeysToBeAdded,不是(numKeysToBeAdded+table.length)>threshold??? 6 if (numKeysToBeAdded > threshold) { 7 int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); 8 if (targetCapacity > MAXIMUM_CAPACITY) 9 targetCapacity = MAXIMUM_CAPACITY; 10 int newCapacity = table.length; 11 while (newCapacity < targetCapacity) 12 newCapacity <<= 1; 13 if (newCapacity > table.length) 14 resize(newCapacity); 15 } 16 17 for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) { 18 Map.Entry<? extends K, ? extends V> e = i.next(); 19 put(e.getKey(), e.getValue()); 20 } 21 }
先回答上面的問題:為什么判斷條件是numKeysToBeAdded,不是(numKeysToBeAdded+table.length)>threshold???
這是一種保守的做法,明顯地,我們應該在(numKeysToBeAdded+table.length)>threshold的時候去拓展容量,但是考慮到將被添加的元素可能會有Key與原本存在的Key相同的情況,所以采用保守的做法,避免拓展到過大的容量。
接着是遍歷m中的內容,然后調用put方法將元素添加到table數組中。
遍歷的時候涉及到了entrySet方法,這個方法定義在Map接口中,HashMap中也有實現,后面會解釋HashMap的這個方法,其它Map的實現暫不解釋。
下面介紹在put方法中被調用到的putForNullKey方法。
1 private V putForNullKey(V value) { 2 for (Entry<K,V> e = table[0]; e != null; e = e.next) { 3 if (e.key == null) { 4 V oldValue = e.value; 5 e.value = value; 6 e.recordAccess(this); 7 return oldValue; 8 } 9 } 10 modCount++; 11 addEntry(0, null, value, 0); 12 return null; 13 }
這是一個私有方法,在put方法中被調用。它首先遍歷table數組,如果找到key為null的元素,則替換元素值並返回oldValue;否則通過addEntry方法添加元素,之后返回null。
還記得上面構造方法中調用到的putAllForCreate嗎?一口氣將put操作的方法看完吧。
1 private void putAllForCreate(Map<? extends K, ? extends V> m) { 2 for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) { 3 Map.Entry<? extends K, ? extends V> e = i.next(); 4 putForCreate(e.getKey(), e.getValue()); 5 } 6 }
先將遍歷的過程放在一邊,因為它同樣涉及到了entrySet()方法。剩下的代碼很簡單,只是調用putForCreate方法逐個元素加入。
1 private void putForCreate(K key, V value) { 2 int hash = (key == null) ? 0 : hash(key.hashCode()); 3 int i = indexFor(hash, table.length); 4 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 5 Object k; 6 if (e.hash == hash && 7 ((k = e.key) == key || (key != null && key.equals(k)))) { 8 e.value = value; 9 return; 10 } 11 } 12 createEntry(hash, key, value, i); 13 }
該方法先計算需要添加的元素的hash值和在table數組中的索引i。接着遍歷table[i]的鏈表,若有元素的key值與傳入key值相等,則替換value,結束方法。若不存在key值相同的元素,則調用createEntry創建並添加元素。
1 void createEntry(int hash, K key, V value, int bucketIndex) { 2 Entry<K,V> e = table[bucketIndex]; 3 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 4 size++; 5 }
這個方法的內容就不解釋了,上面都解釋過。
至此所有put相關操作都解釋完畢了。put之外,另一個常用的操作就是get,下面就來看get方法。
1 public V get(Object key) { 2 if (key == null) 3 return getForNullKey(); 4 int hash = hash(key.hashCode()); 5 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 6 e != null; 7 e = e.next) { 8 Object k; 9 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 10 return e.value; 11 } 12 return null; 13 }
該方法分為key為null和不為null兩塊。先看不為null的情況。先獲取key的hash值,之后通過hash值及table.length獲取key對應的table數組的索引,遍歷索引的鏈表,所找到key相同的元素,則返回元素的value,否者返回null。不為null的情況調用了getForNullKey()方法。
1 private V getForNullKey() { 2 for (Entry<K,V> e = table[0]; e != null; e = e.next) { 3 if (e.key == null) 4 return e.value; 5 } 6 return null; 7 }
這是一個私有方法,只在get中被調用。該方法判斷table[0]中的鏈表是否包含key為null的元素,包含則返回value,不包含則返回null。為什么是遍歷table[0]的鏈表?因為key為null的時候獲得的hash值都是0。
添加(put)和獲取(get)都結束了,接着看如何判斷一個元素是否存在。
HashMap沒有提供判斷元素是否存在的方法,只提供了判斷Key是否存在及Value是否存在的方法,分別是containsKey(Object key)、containsValue(Object value)。
containsKey(Object key)方法很簡單,只是判斷getEntry(key)的結果是否為null,是則返回false,否返回true。
1 public boolean containsKey(Object key) { 2 return getEntry(key) != null; 3 } 4 final Entry<K,V> getEntry(Object key) { 5 int hash = (key == null) ? 0 : hash(key.hashCode()); 6 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 7 e != null; 8 e = e.next) { 9 Object k; 10 if (e.hash == hash && 11 ((k = e.key) == key || (key != null && key.equals(k)))) 12 return e; 13 } 14 return null; 15 }
getEntry(Object key)也沒什么內容,只是根據key對應的hash值計算在table數組中的索引位置,然后遍歷該鏈表判斷是否存在相同的key值。
1 public boolean containsValue(Object value) { 2 if (value == null) 3 return containsNullValue(); 4 5 Entry[] tab = table; 6 for (int i = 0; i < tab.length ; i++) 7 for (Entry e = tab[i] ; e != null ; e = e.next) 8 if (value.equals(e.value)) 9 return true; 10 return false; 11 } 12 private boolean containsNullValue() { 13 Entry[] tab = table; 14 for (int i = 0; i < tab.length ; i++) 15 for (Entry e = tab[i] ; e != null ; e = e.next) 16 if (e.value == null) 17 return true; 18 return false; 19 }
判斷一個value是否存在比判斷key是否存在還要簡單,就是遍歷所有元素判斷是否有相等的值。這里分為兩種情況處理,value為null何不為null的情況,但內容差不多,只是判斷相等的方式不同。
這個判斷是否存在必須遍歷所有元素,是一個雙重循環的過程,因此是比較耗時的操作。
接着看HashMap中“刪除”相關的操作,有remove(Object key)和clear()兩個方法。
remove(Object key)
1 public V remove(Object key) { 2 Entry<K,V> e = removeEntryForKey(key); 3 return (e == null ? null : e.value); 4 }
看這個方法,removeEntryKey(key)的返回結果應該是被移除的元素,如果不存在這個元素則返回為null。remove方法根據removeEntryKey返回的結果e是否為null返回null或e.value。
removeEntryForKey(Object key)
1 final Entry<K,V> removeEntryForKey(Object key) { 2 int hash = (key == null) ? 0 : hash(key.hashCode()); 3 int i = indexFor(hash, table.length); 4 Entry<K,V> prev = table[i]; 5 Entry<K,V> e = prev; 6 7 while (e != null) { 8 Entry<K,V> next = e.next; 9 Object k; 10 if (e.hash == hash && 11 ((k = e.key) == key || (key != null && key.equals(k)))) { 12 modCount++; 13 size--; 14 if (prev == e) 15 table[i] = next; 16 else 17 prev.next = next; 18 e.recordRemoval(this); 19 return e; 20 } 21 prev = e; 22 e = next; 23 } 24 25 return e; 26 }
上面的這個過程就是先找到table數組中對應的索引,接着就類似於一般的鏈表的刪除操作,而且是單向鏈表刪除節點,很簡單。在C語言中就是修改指針,這個例子中就是將要刪除節點的前一節點的next指向刪除被刪除節點的next即可。
clear()
1 public void clear() { 2 modCount++; 3 Entry[] tab = table; 4 for (int i = 0; i < tab.length; i++) 5 tab[i] = null; 6 size = 0; 7 }
clear()方法刪除HashMap中所有的元素,這里就不用一個個刪除節點了,而是直接將table數組內容都置空,這樣所有的鏈表都已經無法訪問,Java的垃圾回收機制會去處理這些鏈表。table數組置空后修改size為0。
這里為什么不直接操作table而是通過tab呢?希望有知道的大俠指點一二。
主要方法看的差不多了,接着看一個上面提到了好幾次但是都擱在一邊沒有分析的方法:entrySet()。
entrySet()
1 public Set<Map.Entry<K,V>> entrySet() { 2 return entrySet0(); 3 } 4 5 private Set<Map.Entry<K,V>> entrySet0() { 6 Set<Map.Entry<K,V>> es = entrySet; 7 return es != null ? es : (entrySet = new EntrySet()); 8 }
為什么會有這樣的方法,只是調用了一下entrySet0,而且entrySet0的名稱看着就很奇怪。再看entrySet0方法中為什么不直接return entrySet!=null?entrySet:(entrySet = new EntrySet)呢?
上面的疑問還沒解開,但是先看entrySet這個屬性吧,在文章開頭的屬性定義中並沒有給出這個屬性,下面先看一下它的定義:
1 private transient Set<Map.Entry<K,V>> entrySet = null;
它是一個內容為Map.Entry<K,V>的Set。看看在哪些地方往里面添加了元素。
為什么上面的那句話我要把它標成紅色?因為這是一個陷阱,在看代碼的時候我就陷進去了。
仔細看EntrySet這個類。
1 private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { 2 public Iterator<Map.Entry<K,V>> iterator() { 3 return newEntryIterator(); 4 } 5 public boolean contains(Object o) { 6 if (!(o instanceof Map.Entry)) 7 return false; 8 Map.Entry<K,V> e = (Map.Entry<K,V>) o; 9 Entry<K,V> candidate = getEntry(e.getKey()); 10 return candidate != null && candidate.equals(e); 11 } 12 public boolean remove(Object o) { 13 return removeMapping(o) != null; 14 } 15 public int size() { 16 return size; 17 } 18 public void clear() { 19 HashMap.this.clear(); 20 } 21 }
看到了什么?這個類根本就沒屬性,它只是個代理。因為它內部類,可以訪問外部類的內容,debug的時候能看到的屬性都是繼承或者外部類的屬性,輸出的時候其實也是調用到了父類的toString方法將HashMap中的內容輸出了。
keySet()
1 public Set<K> keySet() { 2 Set<K> ks = keySet; 3 return (ks != null ? ks : (keySet = new KeySet())); 4 }
是不是和entrySet0()方法很像!
1 private final class KeySet extends AbstractSet<K> { 2 public Iterator<K> iterator() { 3 return newKeyIterator(); 4 } 5 public int size() { 6 return size; 7 } 8 public boolean contains(Object o) { 9 return containsKey(o); 10 } 11 public boolean remove(Object o) { 12 return HashMap.this.removeEntryForKey(o) != null; 13 } 14 public void clear() { 15 HashMap.this.clear(); 16 } 17 }
同樣是個代理類,contains、remove、clear方法都是調用的HashMap的方法。
values()
1 public Collection<V> values() { 2 Collection<V> vs = values; 3 return (vs != null ? vs : (values = new Values())); 4 } 5 6 private final class Values extends AbstractCollection<V> { 7 public Iterator<V> iterator() { 8 return newValueIterator(); 9 } 10 public int size() { 11 return size; 12 } 13 public boolean contains(Object o) { 14 return containsValue(o); 15 } 16 public void clear() { 17 HashMap.this.clear(); 18 } 19 }
values()方法也一樣是代理。只是Values類繼承自AbstractCollention類,而不是AbstractSet。
還有一個重要的內容沒有進行說明,那就是迭代器。HashMap中的entrySet()、keySet()、values()等方法都使用到了迭代器Iterator的知識。其他集合類也有使用到迭代器,將另寫博文總結討論集合類的迭代器。