關於HashMap的遍歷輸出,以及以遍歷形式刪除鍵值對所出現的問題。


  • 一 HashMap遍歷輸出的幾種方式

  1.  foreach 取出map.entrySet()並獲取key和value
    1  Map<String, String> map = new HashMap<String, String>();
    2  for (Entry<String, String> entry : map.entrySet()) {
    3      entry.getKey();
    4      entry.getValue();
    5  }  

     

  2. 調用map.entrySet()的集合迭代器,通過hasNext()方法判斷是否有元素可迭代
    1 Map<String, String> map = new HashMap<String, String>();
    2 Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
    3  while (iterator.hasNext()) {
    4      Map.Entry<String, String> entry = iterator.next();
    5      entry.getKey();
    6      entry.getValue();
    7  }

     

  3. 通過HashMap中的keySet()方法獲取key集合,通過循環獲取value
    1 Map<String, String> map = new HashMap<String, String>();
    2 for (String key : map.keySet()) {
    3     map.get(key);
    4 }

     

  4. 通過臨時變量保存map.entrySet(),遍歷輸出
    1 Map<String, String> map = new HashMap<String, String>();
    2 Set<Entry<String, String>> entrySet = map.entrySet();
    3 for (Entry<String, String> entry : entrySet) {
    4     entry.getKey();
    5     entry.getValue();
    6 }

     

  以上就是常用的四種遍歷輸出hashMap集合的方法,接下來分析一下四種方法的適用性和效率。

  •  二 HashMap常用的四種遍歷方法的分析對比及實用性

     先貼上代碼(代碼參考)

  1 package com.lwu.java.test;
  2  
  3 import java.text.DecimalFormat;
  4 import java.util.Calendar;
  5 import java.util.HashMap;
  6 import java.util.Iterator;
  7 import java.util.Map;
  8 import java.util.Map.Entry;
  9 import java.util.Set;
 10  
 11 /**
 12  * JavaLoopTest
 13  * 
 14  * @author www.trinea.cn 2013-10-28
 15     
 16  */
 17 public class JavaLoopTest {
 18  
 19     public static void main(String[] args) {
 20         System.out.print("compare loop performance of HashMap");
 21         loopMapCompare(getHashMaps(10000, 100000, 1000000, 2000000));
 22     }
 23  
 24     public static Map<String, String>[] getHashMaps(int... sizeArray) {
 25         Map<String, String>[] mapArray = new HashMap[sizeArray.length];
 26         for (int i = 0; i < sizeArray.length; i++) {
 27             int size = sizeArray[i];
 28             Map<String, String> map = new HashMap<String, String>();
 29             for (int j = 0; j < size; j++) {
 30                 String s = Integer.toString(j);
 31                 map.put(s, s);
 32             }
 33             mapArray[i] = map;
 34         }
 35         return mapArray;
 36     }
 37  
 38     public static void loopMapCompare(Map<String, String>[] mapArray) {
 39         printHeader(mapArray);
 40         long startTime, endTime;
 41  
 42         // Type 1
 43         for (int i = 0; i < mapArray.length; i++) {
 44             Map<String, String> map = mapArray[i];
 45             startTime = Calendar.getInstance().getTimeInMillis();
 46             for (Entry<String, String> entry : map.entrySet()) {
 47                 entry.getKey();
 48                 entry.getValue();
 49             }
 50             endTime = Calendar.getInstance().getTimeInMillis();
 51             printCostTime(i, mapArray.length, "for each entrySet", endTime - startTime);
 52         }
 53  
 54         // Type 2
 55         for (int i = 0; i < mapArray.length; i++) {
 56             Map<String, String> map = mapArray[i];
 57             startTime = Calendar.getInstance().getTimeInMillis();
 58             Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
 59             while (iterator.hasNext()) {
 60                 Map.Entry<String, String> entry = iterator.next();
 61                 entry.getKey();
 62                 entry.getValue();
 63             }
 64             endTime = Calendar.getInstance().getTimeInMillis();
 65             printCostTime(i, mapArray.length, "for iterator entrySet", endTime - startTime);
 66         }
 67  
 68         // Type 3
 69         for (int i = 0; i < mapArray.length; i++) {
 70             Map<String, String> map = mapArray[i];
 71             startTime = Calendar.getInstance().getTimeInMillis();
 72             for (String key : map.keySet()) {
 73                 map.get(key);
 74             }
 75             endTime = Calendar.getInstance().getTimeInMillis();
 76             printCostTime(i, mapArray.length, "for each keySet", endTime - startTime);
 77         }
 78  
 79         // Type 4
 80         for (int i = 0; i < mapArray.length; i++) {
 81             Map<String, String> map = mapArray[i];
 82             startTime = Calendar.getInstance().getTimeInMillis();
 83             Set<Entry<String, String>> entrySet = map.entrySet();
 84             for (Entry<String, String> entry : entrySet) {
 85                 entry.getKey();
 86                 entry.getValue();
 87             }
 88             endTime = Calendar.getInstance().getTimeInMillis();
 89             printCostTime(i, mapArray.length, "for entrySet=entrySet()", endTime - startTime);
 90         }
 91     }
 92  
 93     static int                 FIRST_COLUMN_LENGTH = 23, OTHER_COLUMN_LENGTH = 12, TOTAL_COLUMN_LENGTH = 71;
 94     static final DecimalFormat COMMA_FORMAT        = new DecimalFormat("#,###");
 95  
 96     public static void printHeader(Map... mapArray) {
 97         printRowDivider();
 98         for (int i = 0; i < mapArray.length; i++) {
 99             if (i == 0) {
100                 StringBuilder sb = new StringBuilder().append("map size");
101                 while (sb.length() < FIRST_COLUMN_LENGTH) {
102                     sb.append(" ");
103                 }
104                 System.out.print(sb);
105             }
106  
107             StringBuilder sb = new StringBuilder().append("| ").append(COMMA_FORMAT.format(mapArray[i].size()));
108             while (sb.length() < OTHER_COLUMN_LENGTH) {
109                 sb.append(" ");
110             }
111             System.out.print(sb);
112         }
113         TOTAL_COLUMN_LENGTH = FIRST_COLUMN_LENGTH + OTHER_COLUMN_LENGTH * mapArray.length;
114         printRowDivider();
115     }
116  
117     public static void printRowDivider() {
118         System.out.println();
119         StringBuilder sb = new StringBuilder();
120         while (sb.length() < TOTAL_COLUMN_LENGTH) {
121             sb.append("-");
122         }
123         System.out.println(sb);
124     }
125  
126     public static void printCostTime(int i, int size, String caseName, long costTime) {
127         if (i == 0) {
128             StringBuilder sb = new StringBuilder().append(caseName);
129             while (sb.length() < FIRST_COLUMN_LENGTH) {
130                 sb.append(" ");
131             }
132             System.out.print(sb);
133         }
134  
135         StringBuilder sb = new StringBuilder().append("| ").append(costTime).append(" ms");
136         while (sb.length() < OTHER_COLUMN_LENGTH) {
137             sb.append(" ");
138         }
139         System.out.print(sb);
140  
141         if (i == size - 1) {
142             printRowDivider();
143         }
144     }
145 }

 

  1. 測試結果(1,000,000條,3次) 

     除了第三種遍歷方式 通過HashMap中的keySet()方法獲取key集合,通過循環獲取value其余的三種遍歷方式所耗時間差距不大。 

        2. 結果分析

    由於其余三種遍歷方式耗時差距不大,我們單獨拿出與眾不同的那一種遍歷方式分析,揪出其源代碼,找出造成耗時增長的原因。

    相比於其余三種,通過HashMap中的keySet()方法獲取key集合,通過循環獲取value  (以下統稱第三種方式)這種方法使用了keySet()這種方法,那么是不是這個元凶造成了遍歷耗時增長呢?

 

    老規矩,先貼源代碼。

 1 //HashMap entrySet和keySet的源碼
 2 private final class KeyIterator extends HashIterator<K> {
 3     public K next() { 4 return nextEntry().getKey(); 5  } 6 } 7 8 private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { 9 public Map.Entry<K,V> next() { 10 return nextEntry(); 11  } 12 }

     以上兩種分別返回的是keySet() 和 entrySet()返回的set的迭代器。

     兩種方法的區別只是返回值不同,父類相同。理論上講兩種方法的性能應該是相差無幾的,在返回值上第三種方式多了一步getKey()的操作。 

     根據key獲取value的時間復雜成都根據hash算法而產生差異,源碼:

 1 public V get(Object key) {
 2     if (key == null) 3 return getForNullKey(); 4 Entry<K,V> entry = getEntry(key); 5 6 return null == entry ? null : entry.getValue(); 7 } 8 9 /** 10 * Returns the entry associated with the specified key in the 11 * HashMap. Returns null if the HashMap contains no mapping 12 * for the key. 13 */ 14 final Entry<K,V> getEntry(Object key) { 15 int hash = (key == null) ? 0 : hash(key); 16 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 17 e != null; 18 e = e.next) { 19  Object k; 20 if (e.hash == hash && 21 ((k = e.key) == key || (key != null && key.equals(k)))) 22 return e; 23  } 24 return null; 25 }

   get的時間復雜程度取決於for循環的次數。

 

  3.四種遍歷方法的使用總結

     A:單從代碼的角度出發:

      如果只需要key值的而不需要value值的話可以使用:

1 Map<String, String> map = new HashMap<String, String>();
2 for (String key : map.keySet()) {
3 }

        B:從功能性和性能的角度出發:

       在同時需要key值和value值的前提下,無論是性能還是代碼的簡潔性來說,通過HashMap中的keySet()方法獲取key集合,通過循環獲取value  這種方法都是一個比較好的選擇。

1 Map<String, String> map = new HashMap<String, String>();
2 for (Entry<String, String> entry : map.entrySet()) {
3     entry.getKey();
4     entry.getValue();
5 }
  • 三 利用遍歷的方式移除HashMap中的鍵值對

    我們先來做一個猜想,是否可以通過前面的遍歷方式來移除HashMap中的鍵值對?在上面的代碼中加上

  map.remove() ?

    答案是不行的,在運行時會拋出以下異常 java.util.ConcurrentModificationException

at java.util.HashMap$HashIterator.nextNode(Unknown Source)
at java.util.HashMap$EntryIterator.next(Unknown Source)
at java.util.HashMap$EntryIterator.next(Unknown Source)

    根據我從網上查詢碰到同樣情況的解決辦法,他們個給出的是這樣的解釋:

    由於我們在遍歷HashMap的元素過程中刪除了當前所在元素,下一個待訪問的元素的指針也由此丟失了。

    所以我們要換一種遍歷方式,代碼如下:

1 for (Iterator<Map.Entry<String, Integer>> it =myHashMap.entrySet().iterator(); it.hasNext();){ 2     Map.Entry<String, Integer> item = it.next(); 3  it.remove(); 4 } 5 for (Map.Entry<String, Integer> item : myHashMap.entrySet()){ 6  System.out.println(item.getKey()); 7 }

    這種方法能滿足大多數情況下的清除鍵值對的要求,那么對於特殊情況下我們應該怎么解決?

  在HashMap的遍歷中刪除元素的特殊情況 

            這種情況是我在尋找別人碰到的類似的問題的時候發現的解決辦法,所以在這里我就直接照搬了。 (轉自@zhangnf 

Java HashMap 如何正確遍歷並刪除元素

侵刪)

    

    如果你的HashMap中的鍵值同樣是一個HashMap,假設你需要處理的是 HashMap<HashMap<String, Integer>, Double> myHashMap 時,很不碰巧,你可能需要修改myHashMap中的一個項的鍵值HashMap中的某些元素,之后再將其刪除。

 

  這時,單單依靠迭代器的 remove() 方法是不足以將該元素刪除的。

 

  例子如下:

 1 HashMap<HashMap<String, Integer>, Integer> myHashMap = new HashMap<>();
 2 HashMap<String, Integer> temp = new HashMap<>();
 3 temp.put("1", 1);
 4 temp.put("2", 2);
 5 myHashMap.put(temp, 3);
 6 for (Iterator<Map.Entry<HashMap<String, Integer>, Integer>> 
 7     it = myHashMap.entrySet().iterator(); it.hasNext();){
 8     Map.Entry<HashMap<String, Integer>, Integer> item = it.next();
 9     item.getKey().remove("1");
10     System.out.println(myHashMap.size());
11     it.remove();
12     System.out.println(myHashMap.size());
13 }

  結果如下:

1
1

  

  雖然 it.remove(); 被執行,但是並沒有真正刪除元素。

  原因在於期望刪除的元素的鍵值(即 HashMap<String, Integer> temp )被修改過了。


       解決方案:

  既然在這種情況下,HashMap中被修改過的元素不能被刪除,那么不妨直接把待修改的元素直接刪除,再將原本所需要的“修改過”的元素加入HashMap。

  想法很好,代碼如下:

 1 for (Iterator<Map.Entry<HashMap<String, Integer>, Integer>> 
 2     it = myHashMap.entrySet().iterator(); it.hasNext();){
 3     Map.Entry<HashMap<String, Integer>, Integer> item = it.next();
 4     //item.getKey().remove("1");
 5     HashMap<String, Integer> to_put = new HashMap<>(item.getKey());
 6     to_put.remove("1");
 7     myHashMap.put(to_put, item.getValue());
 8     System.out.println(myHashMap.size());
 9     it.remove();
10     System.out.println(myHashMap.size());
11 }

  但是依然是RE:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.remove(Unknown Source)

  原因在於,迭代器遍歷時,每一次調用 next() 函數,至多只能對容器修改一次。上面的代碼則進行了兩次修改:一次添加,一次刪除。


 

  

既然 java.util.ConcurrentModificationException 異常被拋出了,那么去想辦法拿掉這個異常即可。

  最后的最后,我決定棄HashMap轉投ConcurrentHashMap。將myHashMap定義為ConcurrentHashMap之后,其它代碼不動。

  運行結果如下:

2
1

 


免責聲明!

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



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