java源碼分析之HashMap

在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的知識。其他集合類也有使用到迭代器

發佈了68 篇原創文章 · 獲贊 22 · 訪問量 41萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章