天天看點

HashMap源碼分析(基于JDK1.6)

    在Java集合類中最常用的除了ArrayList外,就是HashMap了。本文盡自己所能,盡量詳細的解釋HashMap的源碼。一山還有一山高,有不足之處請之處,定感謝指定并及時修正。

    在看HashMap源碼之前先複習一下資料結構。

    Java最基本的資料結構有數組和連結清單。數組的特點是空間連續(大小固定)、尋址迅速,但是插入和删除時需要移動元素,是以查詢快,增加删除慢。連結清單恰好相反,可動态增加或減少空間以适應新增和删除元素,但查找時隻能順着一個個節點查找,是以增加删除快,查找慢。有沒有一種結構綜合了數組和連結清單的優點呢?當然有,那就是哈希表(雖說是綜合優點,但實際上查找肯定沒有數組快,插入删除沒有連結清單快,一種折中的方式吧)。一般采用拉鍊法實作哈希表。哈希表?拉鍊法?可能一下想不起來,不過放張圖就了然了。

HashMap源碼分析(基于JDK1.6)

    (圖檔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的知識。其他集合類也有使用到疊代器,将另寫博文總結讨論集合類的疊代器。

如果本文對您有幫助,點一下右下角的“推薦”