Map作為鍵值對Entry<K,V>的的容器,對其內部 鍵值對Entry<K,V> 的遍歷總歸是要有一個順序的。
本文重點討論HashMap及其子類LinkedHashMap的遍歷機制,總結出兩者的特點和適用情況。
1.HashMap的遍歷機制
HashMap 提供了兩個遍歷訪問其內部元素Entry<k,v>的接口:
1. Set<Map.Entry<K,V>> entrySet() 返回此映射所包含的映射關系的 Set 視圖。
2. Set<K> keySet() 返回此映射中所包含的鍵的 Set 視圖。
實際上,第二個借口表示的Key的順序,和第一個接口返回的Entry順序是對應的,也就是說:這兩種接口對HashMap的元素遍歷的順序相相同的。 那么,HashMap遍歷內部Entry<K,V> 的順序是什么呢? 搞清楚這個問題,先要知道其內部結構是怎樣的。
HashMap內部對鍵值對的存儲結構使用的是數組+鏈表的形式。其結構如下圖所示:
HashMap內部Entry<K,V>的遍歷順序:
對Entry[] table 數組,從index=0開始,依次遍歷table[i] 上的鏈表上的Entry對象。
由於HashMap在存儲Entry對象的時候,是根據Key的hash值判定存儲到Entry[] table數組的哪一個索引值表示的鏈表上,所以籠統地說就是:使用hashMap.put(Key key,Value value)會將 對應的Entry<Key,Value>對象隨機地分配到某個Entry[] table數組的元素表示的鏈表上。換一句話說就是:
對HashMap遍歷Entry對象的順序和Entry對象的存儲順序之間沒有任何關系。
但是,我們有時候想要遍歷HashMap的元素Entry的順序和其存儲的順序一致,HashMap顯然不能滿足條件了。而LinkedHashMap則可以滿足這個需要。
2. LinkedHashMap 的遍歷機制
LinkedHashMap 是HashMap的子類,它可以實現對容器內Entry的存儲順序和對Entry的遍歷順序保持一致。
為了實現這個功能,LinkedHashMap內部使用了一個Entry類型的雙向鏈表,用這個雙向鏈表記錄Entry的存儲順序。當需要對該Map進行遍歷的時候,實際上是遍歷的是這個雙向鏈表。
LinkedHashMap內部使用的LinkedHashMap.Entry類繼承自 Map.Ent ry類,在其基礎上增加了LinkedHashMap.Entry類型的兩個字段,用來引用該Entry在雙向鏈表中的前面的Entry對象和后面的Entry對象。
它的內部會在 Map.Entry 類的基礎上,增加兩個Entry類型的引用:before,after。LinkedHashMap使用一個雙向連表,將其內部所有的Entry串起來。
我們將通過以下例子,來了解內部雙向鏈表是怎樣構造的:
- LinkedHashMap linkedHashMap = new LinkedHashMap();
- linkedHashMap.put("name","louis");
- linkedHashMap.put("age","24");
- linkedHashMap.put("sex","male");
上述的代碼除了會將對應的Entry對象放置到在Entry[] table 表示的數組鏈表中外,還會將該Entry對象添加到其內部維護的雙向鏈表中。對應的LinkedHashMap內部的雙向鏈表變化如下:
對LinkedHashMap進行遍歷的策略:
從 header.after 指向的Entry對象開始,然后一直沿着此鏈表 遍歷下去,直到某個entry.after == header 為止,完成遍歷。
由此,就可以保證遍歷LinkedHashMap內元素的順序,就是Entry插入到LinkedHashMap中的順序。
將上面代碼中定義的linkedHashMap 遍歷輸出,會發現遍歷的順序跟插入的順序完全一致:
結果輸出:
- Iterator<Map.Entry> iterator= linkedHashMap.entrySet().iterator();
- while(iterator.hasNext())
- {
- Map.Entry entry = iterator.next();
- System.out.println(entry.getKey()+":"+entry.getValue());
- }
根據Entry<K,V>插入LinkedHashMap的順序進行遍歷的方式叫做:按插入順序遍歷。
另外,LinkedHashMap還支持一種遍歷順序,叫做:Get讀取順序。
如果LinkedHashMap的這個Get讀取遍歷順序開啟,那么,當我們在LinkedHashMap上調用get(key) 方法時,會導致內部 key對應的Entry在雙向鏈表中的位置移動到雙向鏈表的最后。
比如,如果當前LinkedHashMap內部的雙向鏈表的情況如下:
相關代碼如下:
- //默認情況下LinkedHashMap的遍歷模式是插入模式,如果想顯式地指定為get讀取模式,那么要將
- //其構造方法的參數置為true,(false 表示的是插入模式)
- LinkedHashMap linkedHashMap = new LinkedHashMap(16, (float) 0.75,true);
- linkedHashMap.put("name","louis");
- linkedHashMap.put("age","24");
- linkedHashMap.put("sex","male");
- linkedHashMap.get("name");//get()方法調用,導致對應的entry移動到雙向鏈表的最后位置
- Iterator<Map.Entry> iterator= linkedHashMap.entrySet().iterator();
- while(iterator.hasNext())
- {
- Map.Entry entry = iterator.next();
- System.out.println(entry.getKey()+":"+entry.getValue());
- }
![]()
3. 總結
1.HashMap對元素的遍歷順序跟Entry插入的順序無關,而LinkedHashMap對元素的遍歷順序可以跟Entry<K,V>插入的順序保持一致。
2.當LinkedHashMap處於Get獲取順序遍歷模式下,當執行get() 操作時,會將對應的Entry<k,v>移到遍歷的最后位置。
3.LinkedHashMap處於按插入順序遍歷的模式下,如果新插入的<key,value> 對應的key已經存在,對應的Entry在遍歷順序中的位置並不會改變。
4. 除了遍歷順序外,其他特性HashMap和LinkedHashMap基本相同。