-
一 HashMap遍历输出的几种方式
-
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 }
-
调用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 }
-
通过HashMap中的keySet()方法获取key集合,通过循环获取value
1 Map<String, String> map = new HashMap<String, String>(); 2 for (String key : map.keySet()) { 3 map.get(key); 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,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