遍历HashMap

3758

HashMap 中迭代项的最佳方法是什么?


7
我需要获取键和值,并将它们添加到一个多维数组中。 - burntsugar
8
Java 8 中使用 Lambda 表达式的方法:https://dev59.com/zW855IYBdhLWcg3woV0_#25616206 - Nitin Mahesh
7个回答

5371

如果你只对键感兴趣,你可以遍历地图的keySet()

Map<String, Object> map = ...;

for (String key : map.keySet()) {
    // ...
}
如果你仅需要值,请使用 values() 方法。
for (Object value : map.values()) {
    // ...
}

如果你希望同时获取键和值,可以使用entrySet()

for (Map.Entry<String, Object> entry : map.entrySet()) {
    String key = entry.getKey();
    Object value = entry.getValue();
    // ...
}

一个注意事项:如果你想在迭代过程中删除元素,你需要通过 Iterator 来执行(参见 karim79 的答案)。但是,更改元素的值是可以的(参见 Map.Entry)。


3
如何同时遍历2个Map?可以使用entrySet()方法吗?我尝试使用&&,但它没有起作用。 - DaMainBoss
2
使用两个迭代器。请参考已接受的答案,了解迭代器的示例用法。 - harto
21
当你需要获取键和值时,使用entrySet会更加高效。如果你只需要其中之一,只需使用对应的方法:https://dev59.com/3G865IYBdhLWcg3wU8-I#6927754 - rogerdpack
4
还有一个重要的点,通过调用keySet()返回的Set和values()返回的Collection都是由原始Map支持的。也就是说,如果你在它们中做出任何修改,这些修改将反映回Map中,但这两种数据结构都不支持add()和addAll()方法,也就是说,你不能向Set中添加新的键或向Collection中添加新的值。 - sactiw
@MarcoSulla 这样的性能问题取决于具体情况,需要进行测量。使用entrySet可能更快,因为它可以避免为每个条目执行查找操作。特定Map的实现决定了哪种方式更好,以及优势有多大。此时的GC压力也可能是一个因素。 - doug65536
显示剩余4条评论

3451

按以下方式遍历entrySet()

public static void printMap(Map mp) {
    Iterator it = mp.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry pair = (Map.Entry)it.next();
        System.out.println(pair.getKey() + " = " + pair.getValue());
        it.remove(); // avoids a ConcurrentModificationException
    }
}

阅读有关 Map 的更多信息。


40
虽然老式写法,但这将有助于避免下面答案中新的foreach写法产生ConcurrentModificationExceptions异常。例如,您可以通过单独使用迭代器来删除。 - Benjamin Wootton
471
@karim79 你认为以下方式怎么样:Map<Integer, Integer> map = new HashMap<Integer, Integer>(); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); } - fresh_dev
15
如果你调用了 "it.remove();",你就会清空这个 map 的内容,如果这个 map 是一个类变量,就不能再次使用了。你有什么解决办法吗? - vim
28
你的意思是要解决这个问题?只需要删除 it.remove(); 这一行即可。 - Danny
114
使用for (Map.Entry<String, Object> cursor : map.entrySet()) {...}语法更佳。 - Chad Okere
显示剩余23条评论

873

从参考资料如何在Java中迭代Map中提取:

在Java中,有几种迭代Map的方法。让我们介绍最常见的方法并审查它们的优缺点。由于Java中的所有映射都实现了Map接口,因此以下技术将适用于任何映射实现(HashMapTreeMapLinkedHashMapHashtable等)。

方法#1:使用For-Each循环迭代条目。

这是最常见的方法,在大多数情况下都比较优选。如果需要在循环中同时使用映射键和值,则应使用此方法。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

请注意,For-Each循环是在Java 5中引入的,因此此方法仅适用于语言的较新版本。此外,如果您尝试迭代空映射,则For-Each循环将抛出NullPointerException,因此在迭代之前应始终检查空引用。
第二种方法:使用For-Each循环遍历键或值。
如果您只需要地图中的键或值,则可以迭代keySet或值而不是entrySet。
Map<Integer, Integer> map = new HashMap<Integer, Integer>();

// Iterating over keys only
for (Integer key : map.keySet()) {
    System.out.println("Key = " + key);
}

// Iterating over values only
for (Integer value : map.values()) {
    System.out.println("Value = " + value);
}

这种方法比使用entrySet迭代略微快一些(大约快10%),并且更加简洁。

方法3:使用迭代器进行迭代。

使用泛型:

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry<Integer, Integer> entry = entries.next();
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

没有泛型:

Map map = new HashMap();
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Map.Entry entry = (Map.Entry) entries.next();
    Integer key = (Integer)entry.getKey();
    Integer value = (Integer)entry.getValue();
    System.out.println("Key = " + key + ", Value = " + value);
}

你也可以使用同样的技巧来遍历keySet或values。

这种方法看起来可能有些冗余,但它有其自身的优点。首先,这是在旧版本的Java中遍历Map的唯一方式。另一个重要特性是,这是唯一允许你在迭代期间通过调用iterator.remove()从Map中删除条目的方法。如果你在For-Each迭代期间尝试这样做,根据Javadoc,你将得到"不可预测的结果"。

从性能角度来看,这种方法与For-Each迭代相等。

方法#4:遍历键并搜索值(效率低下)。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (Integer key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println("Key = " + key + ", Value = " + value);
}

这看起来可能是方法#1的一个更清晰的替代方案,但实际上它相当缓慢和低效,因为通过键获取值可能会耗费时间(在不同的Map实现中,该方法比方法#1慢20%-200%)。如果您安装了FindBugs,则会检测到此情况并警告您迭代效率低下。应避免使用此方法。
结论:
如果您只需要地图中的键或值,请使用方法#2。如果您被困在旧版Java(小于5)中或计划在迭代期间删除条目,则必须使用方法#3。否则,请使用方法#1。

1
让我们加上一个小注意事项,对于ConcurrentMap而言,在keySet()上进行迭代通常会崩溃(无法保证早期收集的键存在相应的值)。另一方面,使用迭代器或条目是安全的(它们始终引用现有对象)。 - P Marecki
3
方法四怎么可能效率低呢?按照定义,对于 HashMap 调用 get() 总是 O(1),这就是 HashMap 的定义,而且用户要求使用 HashMap。我不明白为什么会有这么高的赞。如果你要引用别人的链接,请确保它与问题有实际关联。 - ohbrobig
1
@ohbrobig,虽然它的运行时间是O(1),但这只是它的运行时间,也是它的可扩展性。这并不意味着它一定会在第一个周期内获得值。方法#4肯定比方法#1慢。 - user961954

184
for (Map.Entry<String, String> item : hashMap.entrySet()) {
    String key = item.getKey();
    String value = item.getValue();
}

107

您可以通过多种方式遍历Map中的条目。像这样获取每个键和值:

Map<?,?> map = new HashMap<Object, Object>();
for(Entry<?, ?> e: map.entrySet()){
    System.out.println("Key " + e.getKey());
    System.out.println("Value " + e.getValue());
}

或者您可以使用以下方法获取键列表:

Collection<?> keys = map.keySet();
for(Object key: keys){
    System.out.println("Key " + key);
    System.out.println("Value " + map.get(key));
}

如果你只想获取所有值,而不关心键,可以使用以下方法:

Collection<?> values = map.values();

76

更聪明:

for (String key : hashMap.keySet()) {
    System.out.println("Key: " + key + ", Value: " + map.get(key));
}

10
实际上这取决于你是否需要这些键。如果不需要,使用entrySet()更有效率,因为此时不会调用hashCode()。 - icfantv
17
每次迭代使用map.get(key)并不更加聪明,而是会使速度变慢。 - CompEng88
2
map.entrySet() 返回已经包含键和值的条目。这样,您就不必调用 hashCode() 并在迭代期间搜索哈希表了。 - CompEng88
Java 8语法。可能仍无法用于Android开发。“Android不打算与任何Java SE API版本完全兼容,包括6或8或任何版本。JRE是Java运行时环境,而JDK是Java开发工具包。除了现有的Android SDK之外,您需要JDK来进行Android应用程序开发。2013年12月9日”[来源](https://dev59.com/vWIj5IYBdhLWcg3wPy0a) - jasonleonhard

56

这要看情况而定。如果你需要访问每个entry的key和value,那么使用entrySet方法。如果你只需要values,可以使用values()方法。如果你只需要keys,可以使用keyset()方法。

一个不好的实践是通过迭代所有的keys,并在循环内部始终使用map.get(key)来获取value。如果你正在这样做,那么第一种我提到的方法适合你。


1
还有一个重要的点,由keySet()返回的Set和由values()返回的Collection都是由原始Map支持的。也就是说,如果你对它们进行任何修改,它们将反映回Map中,但是它们都不支持add()和addAll()方法,也就是说,你不能将新键添加到Set或新的值添加到Collection中。 - sactiw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接