關於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()
函數中調用了HashIterator
的nextEntry()
方法。所以,重點就變成了分析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內部的存儲方式是一致的。