簡單分析Java的HashMap.entrySet()的實現


關於Java的HashMap.entrySet(),文檔是這樣描述的:這個方法返回一個Set,這個Set是HashMap的視圖,對Map的操作會在Set上反映出來,反過來也是。原文是

Returns a Set view of the mappings contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa.

本文通過源碼簡單分析這一功能的實現。

首先要簡單介紹一下HashMap的內部存儲。我們知道,Map是用來存儲key-value類型數據的,一個<k, v>對在Map的接口定義中被定義為Entry,HashMap內部實現了Entry接口。HashMap內部維護一個Entry數組。

transient Entry[] table;

當put一個新元素的時候,根據key的hash值計算出對應的數組下標。數組的每個元素是一個鏈表的頭指針,用來存儲具有相同下標的Entry。

Entry[] table
    +---+
    | 0 | -> entry_0_0 -> entry_0_1 -> null
    +---+
    | 1 | -> null
    +---+
    |   |

     ...

    |n-1| -> entry_n-1_0 -> null
    +---+

entrySet()方法返回的是一個特殊的Set,定義為HashMap的內部私有類

private final class EntrySet extends AbstractSet<Map.Entry<K,V>>

主要看一下這個Set的iterator()方法。這個方法很簡單,返回一個EntryIterator類型的實例。EntryIterator類型是泛型HashIterator<T>的一個子類,這個類的內容很簡單,唯一的代碼是在next()函數中調用了HashIteratornextEntry()方法。所以,重點就變成了分析nextEntry()方法。上述過程見下面的圖示

HashMap
    |- table <------------------------------------\
    \+ entrySet()                                 |iterates
        |              HashMap.HashIterator<T>    |
        |returns                ^       \- nextEntry()
        V                       -                 ^
HashMap.EntrySet                |                 |
    \- iterator()               |extends          |
            |                   |                 |
            |  instantiats      |                 |calls
            \----------> HashMap.EntryIterator    |
                                        \- next() /

HashIterator通過遍歷table數組,實現對HashMap的遍歷。內部維護幾個變量:index記錄當前在table數組中的下標,current用來記錄當前在table[index]這個鏈表中的位置,next指向current的下一個元素。nextEntry()的完整代碼如下:

final Entry<K,V> nextEntry() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    Entry<K,V> e = next;
    if (e == null)
        throw new NoSuchElementException();

    if ((next = e.next) == null) {
        Entry[] t = table;
        while (index < t.length && (next = t[index++]) == null)
            ;
    }
    current = e;
    return e;
}

第一個if用來判斷在多線程的情況下是否出現並發錯誤,這里暫時不討論。如果next不是null,那么返回並更新next。更新方法是第三個if的內容:如果當前鏈表還沒有結束,則簡單的把next向后移一個;否則在table中查找下一個非空的slot。

總結一下,HashMap的entrySet()方法返回一個特殊的Set,這個Set使用EntryIterator遍歷,而這個Iterator則直接操作於HashMap的內部存儲結構table上。通過這種方式實現了“視圖”的功能。整個過程不需要任何輔助存儲空間。

p.s. 從這一點也可以看出為什么entrySet()是遍歷HashMap最高效的方法,原因很簡單,因為這種方式和HashMap內部的存儲方式是一致的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM