關於HashMap的遍歷輸出,以及以遍歷形式刪除鍵值對所出現的問題。 Java 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集合的方法,接下來分析一下四種方法的適用性和效率。html

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

     先貼上代碼(代碼參考)java

  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. 結果分析app

    因爲其他三種遍歷方式耗時差距不大,咱們單獨拿出不同凡響的那一種遍歷方式分析,揪出其源代碼,找出形成耗時增加的緣由。ide

    相比於其他三種,經過HashMap中的keySet()方法獲取key集合,經過循環獲取value  (如下統稱第三種方式)這種方法使用了keySet()這種方法,那麼是否是這個元兇形成了遍歷耗時增加呢?函數

 

    老規矩,先貼源代碼。oop

 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的迭代器。post

     兩種方法的區別只是返回值不一樣,父類相同。理論上講兩種方法的性能應該是相差無幾的,在返回值上第三種方式多了一步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
相關文章
相關標籤/搜索