-
一 HashMap遍历输出的几种方式
- foreach 取出map.entrySet()并获取key和value
1 Map
map = new HashMap ();2 for (Entry entry : map.entrySet()) {3 entry.getKey();4 entry.getValue();5 } - 调用map.entrySet()的集合迭代器,通过hasNext()方法判断是否有元素可迭代
1 Map
map = new HashMap ();2 Iterator > iterator = map.entrySet().iterator();3 while (iterator.hasNext()) {4 Map.Entry entry = iterator.next();5 entry.getKey();6 entry.getValue();7 } - 通过HashMap中的keySet()方法获取key集合,通过循环获取value
1 Map
map = new HashMap ();2 for (String key : map.keySet()) {3 map.get(key);4 } - 通过临时变量保存map.entrySet(),遍历输出
1 Map
map = new HashMap ();2 Set > entrySet = map.entrySet();3 for (Entry 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[] getHashMaps(int... sizeArray) { 25 Map [] mapArray = new HashMap[sizeArray.length]; 26 for (int i = 0; i < sizeArray.length; i++) { 27 int size = sizeArray[i]; 28 Map map = new HashMap (); 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 [] mapArray) { 39 printHeader(mapArray); 40 long startTime, endTime; 41 42 // Type 1 43 for (int i = 0; i < mapArray.length; i++) { 44 Map map = mapArray[i]; 45 startTime = Calendar.getInstance().getTimeInMillis(); 46 for (Entry 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 map = mapArray[i]; 57 startTime = Calendar.getInstance().getTimeInMillis(); 58 Iterator > iterator = map.entrySet().iterator(); 59 while (iterator.hasNext()) { 60 Map.Entry 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 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 map = mapArray[i]; 82 startTime = Calendar.getInstance().getTimeInMillis(); 83 Set > entrySet = map.entrySet(); 84 for (Entry 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{ 3 public K next() { 4 return nextEntry().getKey(); 5 } 6 } 7 8 private final class EntryIterator extends HashIterator > { 9 public Map.Entry 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 Entryentry = 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 getEntry(Object key) { 15 int hash = (key == null) ? 0 : hash(key); 16 for (Entry 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 Mapmap = new HashMap ();2 for (String key : map.keySet()) {3 }
B:从功能性和性能的角度出发:
在同时需要key值和value值的前提下,无论是性能还是代码的简洁性来说,通过HashMap中的keySet()方法获取key集合,通过循环获取value 这种方法都是一个比较好的选择。
1 Mapmap = new HashMap ();2 for (Entry 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> it =myHashMap.entrySet().iterator(); it.hasNext();){2 Map.Entry item = it.next();3 it.remove();4 }5 for (Map.Entry item : myHashMap.entrySet()){6 System.out.println(item.getKey());7 }
这种方法能满足大多数情况下的清除键值对的要求,那么对于特殊情况下我们应该怎么解决?
在HashMap的遍历中删除元素的特殊情况
这种情况是我在寻找别人碰到的类似的问题的时候发现的解决办法,所以在这里我就直接照搬了。 (转自@
侵删)
如果你的HashMap中的键值同样是一个HashMap,假设你需要处理的是 HashMap<HashMap<String, Integer>, Double> myHashMap 时,很不碰巧,你可能需要修改myHashMap中的一个项的键值HashMap中的某些元素,之后再将其删除。
这时,单单依靠迭代器的 remove() 方法是不足以将该元素删除的。
例子如下:
1 HashMap, Integer> myHashMap = new HashMap<>(); 2 HashMap temp = new HashMap<>(); 3 temp.put("1", 1); 4 temp.put("2", 2); 5 myHashMap.put(temp, 3); 6 for (Iterator , Integer>> 7 it = myHashMap.entrySet().iterator(); it.hasNext();){ 8 Map.Entry , 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 }
结果如下:
11
虽然 it.remove(); 被执行,但是并没有真正删除元素。
原因在于期望删除的元素的键值(即 HashMap<String, Integer> temp )被修改过了。
解决方案:
既然在这种情况下,HashMap中被修改过的元素不能被删除,那么不妨直接把待修改的元素直接删除,再将原本所需要的“修改过”的元素加入HashMap。
想法很好,代码如下:
1 for (Iterator, Integer>> 2 it = myHashMap.entrySet().iterator(); it.hasNext();){ 3 Map.Entry , Integer> item = it.next(); 4 //item.getKey().remove("1"); 5 HashMap 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之后,其它代码不动。
运行结果如下:
21