关于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

 

posted @ 2017-11-28 10:19  钟离i  阅读(4034)  评论(3编辑  收藏  举报