题 如何有效地迭代“地图”中的每个条目?


如果我有一个实现的对象 Map Java中的接口,我希望迭代其中包含的每一对,通过地图的最有效方法是什么?

元素的排序是否取决于我对界面的具体映射实现?


2529
2017-09-05 21:12


起源


在Java 8中使用Lambda Expression: stackoverflow.com/a/25616206/1503859 - Nitin Mahesh


答案:


Map<String, String> map = ...
for (Map.Entry<String, String> entry : map.entrySet())
{
    System.out.println(entry.getKey() + "/" + entry.getValue());
}

4093
2017-09-05 21:15



如果你这样做,那么它将无法工作,因为Entry是Map中的嵌套类。 java.sun.com/javase/6/docs/api/java/util/Map.html - ScArcher2
你可以将导入编写为“import java.util.Map.Entry;”它会起作用。 - jjujuma
@Pureferret你可能想要使用迭代器的唯一原因是你需要调用它 remove 方法。如果是这样的话, 这个答案 告诉你如何做到这一点。否则,如上面的答案中所示的增强循环是要走的路。 - assylias
我相信Map.Entry形式比将内部类导入当前命名空间更清晰。 - Josiah Yoder
请注意,您可以使用 map.values() 要么 map.keySet() 如果您只想循环遍历值或键。 - dguay


总结其他答案并将它们与我所知道的结合起来,我找到了10种主要方法(见下文)。另外,我写了一些性能测试(见下面的结果)。例如,如果我们想要找到地图的所有键和值的总和,我们可以写:

  1. 运用 迭代器 和 Map.Entry的

    long i = 0;
    Iterator<Map.Entry<Integer, Integer>> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<Integer, Integer> pair = it.next();
        i += pair.getKey() + pair.getValue();
    }
    
  2. 运用 的foreach 和 Map.Entry的

    long i = 0;
    for (Map.Entry<Integer, Integer> pair : map.entrySet()) {
        i += pair.getKey() + pair.getValue();
    }
    
  3. 运用 的forEach 来自Java 8

    final long[] i = {0};
    map.forEach((k, v) -> i[0] += k + v);
    
  4. 运用 中的keySet 和 的foreach

    long i = 0;
    for (Integer key : map.keySet()) {
        i += key + map.get(key);
    }
    
  5. 运用 中的keySet 和 迭代器

    long i = 0;
    Iterator<Integer> itr2 = map.keySet().iterator();
    while (itr2.hasNext()) {
        Integer key = itr2.next();
        i += key + map.get(key);
    }
    
  6. 运用 对于 和 Map.Entry的

    long i = 0;
    for (Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator(); entries.hasNext(); ) {
        Map.Entry<Integer, Integer> entry = entries.next();
        i += entry.getKey() + entry.getValue();
    }
    
  7. 使用Java 8 流API

    final long[] i = {0};
    map.entrySet().stream().forEach(e -> i[0] += e.getKey() + e.getValue());
    
  8. 使用Java 8 流API并行

    final long[] i = {0};
    map.entrySet().stream().parallel().forEach(e -> i[0] += e.getKey() + e.getValue());
    
  9. 运用 IterableMap 的 Apache Collections

    long i = 0;
    MapIterator<Integer, Integer> it = iterableMap.mapIterator();
    while (it.hasNext()) {
        i += it.next() + it.getValue();
    }
    
  10. 运用 MutableMap Eclipse(CS)集合

    final long[] i = {0};
    mutableMap.forEachKeyValue((key, value) -> {
        i[0] += key + value;
    });
    

性能测试 (mode = AverageTime,system = Windows 8.1 64-bit,Intel i7-4790 3.60 GHz,16 GB)

  1. 对于小地图(100个元素),得分0.308是最好的

    Benchmark                          Mode  Cnt  Score    Error  Units
    test3_UsingForEachAndJava8         avgt  10   0.308 ±  0.021  µs/op
    test10_UsingEclipseMap             avgt  10   0.309 ±  0.009  µs/op
    test1_UsingWhileAndMapEntry        avgt  10   0.380 ±  0.014  µs/op
    test6_UsingForAndIterator          avgt  10   0.387 ±  0.016  µs/op
    test2_UsingForEachAndMapEntry      avgt  10   0.391 ±  0.023  µs/op
    test7_UsingJava8StreamApi          avgt  10   0.510 ±  0.014  µs/op
    test9_UsingApacheIterableMap       avgt  10   0.524 ±  0.008  µs/op
    test4_UsingKeySetAndForEach        avgt  10   0.816 ±  0.026  µs/op
    test5_UsingKeySetAndIterator       avgt  10   0.863 ±  0.025  µs/op
    test8_UsingJava8StreamApiParallel  avgt  10   5.552 ±  0.185  µs/op
    
  2. 对于10000个元素的地图,得分37.606是最好的

    Benchmark                           Mode   Cnt  Score      Error   Units
    test10_UsingEclipseMap              avgt   10    37.606 ±   0.790  µs/op
    test3_UsingForEachAndJava8          avgt   10    50.368 ±   0.887  µs/op
    test6_UsingForAndIterator           avgt   10    50.332 ±   0.507  µs/op
    test2_UsingForEachAndMapEntry       avgt   10    51.406 ±   1.032  µs/op
    test1_UsingWhileAndMapEntry         avgt   10    52.538 ±   2.431  µs/op
    test7_UsingJava8StreamApi           avgt   10    54.464 ±   0.712  µs/op
    test4_UsingKeySetAndForEach         avgt   10    79.016 ±  25.345  µs/op
    test5_UsingKeySetAndIterator        avgt   10    91.105 ±  10.220  µs/op
    test8_UsingJava8StreamApiParallel   avgt   10   112.511 ±   0.365  µs/op
    test9_UsingApacheIterableMap        avgt   10   125.714 ±   1.935  µs/op
    
  3. 对于具有100000个元素的地图,得分11​​84.767是最好的

    Benchmark                          Mode   Cnt  Score        Error    Units
    test1_UsingWhileAndMapEntry        avgt   10   1184.767 ±   332.968  µs/op
    test10_UsingEclipseMap             avgt   10   1191.735 ±   304.273  µs/op
    test2_UsingForEachAndMapEntry      avgt   10   1205.815 ±   366.043  µs/op
    test6_UsingForAndIterator          avgt   10   1206.873 ±   367.272  µs/op
    test8_UsingJava8StreamApiParallel  avgt   10   1485.895 ±   233.143  µs/op
    test5_UsingKeySetAndIterator       avgt   10   1540.281 ±   357.497  µs/op
    test4_UsingKeySetAndForEach        avgt   10   1593.342 ±   294.417  µs/op
    test3_UsingForEachAndJava8         avgt   10   1666.296 ±   126.443  µs/op
    test7_UsingJava8StreamApi          avgt   10   1706.676 ±   436.867  µs/op
    test9_UsingApacheIterableMap       avgt   10   3289.866 ±  1445.564  µs/op
    

图表(性能测试取决于地图大小)

Enter image description here

表(性能测试取决于地图大小)

          100     600      1100     1600     2100
test10    0.333    1.631    2.752    5.937    8.024
test3     0.309    1.971    4.147    8.147   10.473
test6     0.372    2.190    4.470    8.322   10.531
test1     0.405    2.237    4.616    8.645   10.707
test2     0.376    2.267    4.809    8.403   10.910
test7     0.473    2.448    5.668    9.790   12.125
test9     0.565    2.830    5.952   13.220   16.965
test4     0.808    5.012    8.813   13.939   17.407
test5     0.810    5.104    8.533   14.064   17.422
test8     5.173   12.499   17.351   24.671   30.403

所有测试都已开启 GitHub上


677
2018-02-22 16:37



@Viacheslav:非常好的答案。只是想知道Java8 apis如何在你的基准测试中通过捕获lambdas来阻碍...(例如 long sum = 0; map.forEach( /* accumulate in variable sum*/); 抓住了 sum 很长,这可能比说慢 stream.mapToInt(/*whatever*/).sum 例如。当然,你不能总是避免捕获状态,但这可能是替补席上的一个合理的补充。 - GPI
你的8测试是错误的。它不同步地从不同的线程访问同一个变量。改成 AtomicInteger 解决问题。 - talex
@ZhekaKozlov:看看那些大错误的错误值。考虑一下测试结果 x±e 暗示在间隔内有结果 x-e 至 x+e,所以最快的结果(1184.767±332.968)范围从 852 至 1518,而第二慢(1706.676±436.867)之间运行 1270 和 2144,结果仍然显着重叠。现在看看最慢的结果, 3289.866±1445.564,这意味着之间的分歧 1844 和 4735 你呢 知道 这些测试结果毫无意义。 - Holger
关于什么 map.entrySet().stream().[parallel().]mapToInt(e -> e.getKey() + e.getValue()).sum();? - glglgl
那么比较3个主要实现:HashMap,LinkedHashMap和TreeMap? - Thierry


在Java 8中,您可以使用新的lambdas功能清洁和快速地执行此操作:

 Map<String,String> map = new HashMap<>();
 map.put("SomeKey", "SomeValue");
 map.forEach( (k,v) -> [do something with key and value] );

 // such as
 map.forEach( (k,v) -> System.out.println("Key: " + k + ": Value: " + v));

的类型 k 和 v 将由编译器推断,无需使用 Map.Entry 了。

十分简单!


227
2017-10-21 10:15



根据您要对地图执行的操作,您还可以对返回的条目使用流API map.entrySet().stream()  docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html - Vitalii Fedorenko
如果你想在forEach()中引用lambda表达式之外声明的非final变量,这将不起作用... - Chris
@Chris正确。如果你尝试使用它将无法工作 实际上是非最终的 来自lambda之外的变量。 - Austin Powers


是的,订单取决于具体的Map实施。

@ ScArcher2具有更优雅的Java 1.5语法。在1.4中,我会做这样的事情:

Iterator entries = myMap.entrySet().iterator();
while (entries.hasNext()) {
  Entry thisEntry = (Entry) entries.next();
  Object key = thisEntry.getKey();
  Object value = thisEntry.getValue();
  // ...
}

196
2017-09-05 21:15



首选for循环而不是while ... for(Iterator entries = myMap.entrySet()。iterator(); entries.hasNext();){...}使用此语法,'entries'范围仅减少为for循环。 - HanuAthena
@jpredham你是对的,使用了 for 构造为 for (Entry e : myMap.entrySet) 不会允许你修改集合,但是@HanuAthena的例子提到它应该可以工作,因为它给你 Iterator 在适用范围。 (除非我遗漏了什么...) - pkaeding
IntelliJ给了我错误 Entry thisEntry = (Entry) entries.next();:不承认 Entry。那个伪代码是为了别的吗? - JohnK
@JohnK尝试导入 java.util.Map.Entry。 - pkaeding
使用此解决方案,键和值的类型无关紧要。谢谢。 - Andreas L.


迭代地图的典型代码是:

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

HashMap 是规范的地图实现,并没有做出保证(或者如果没有对它执行变异操作,它不应该改变顺序)。 SortedMap 将根据键的自然顺序返回条目,或者a Comparator,如果提供的话。 LinkedHashMap 将按照插入顺序或访问顺序返回条目,具体取决于它的构造方式。 EnumMap 按自然顺序返回条目。

(更新:我认为这不再是真的。) 注意, IdentityHashMap  entrySet 迭代器目前有一个特殊的实现,返回相同的 Map.Entry 每个项目的实例 entrySet!但是,每当一个新的迭代器推进时 Map.Entry 已更新。


101
2017-09-05 21:26



EnumMap还具有IdentityHashMap的这种特殊行为 - Premraj
“LinkedHashMap将返回[...] access-order [...]中的条目”...所以您按照访问顺序访问元素?要么是重复,要么是有趣的,可以使用题外话。 ;-) - jpaugh
@jpaugh只能直接访问 LinkedHashMap 计数。那些通过 iterator, spliterator, entrySet等,不要修改订单。 - Tom Hawtin - tackline
听起来它可能非常有用;或者,一种很好的方式来污蔑自己。感谢有趣的小贴士! - jpaugh
1。 虽然 → 如果? 2.最后一段可能会受益于清理。 - Peter Mortensen


使用迭代器和泛型的示例:

Iterator<Map.Entry<String, String>> entries = myMap.entrySet().iterator();
while (entries.hasNext()) {
  Map.Entry<String, String> entry = entries.next();
  String key = entry.getKey();
  String value = entry.getValue();
  // ...
}

89
2017-08-18 17:34



你应该放 Iterator 在for循环中限制其范围。 - Steve Kuo
@SteveKuo“限制范围”是什么意思? - StudioWorks
@StudioWorks for (Iterator<Map.Entry<K, V>> entries = myMap.entrySet().iterator(); entries.hasNext(); ) { Map.Entry<K, V> entry = entries.next(); }。通过使用该构造,我们限制了(变量的可见性)的范围 entries 到for循环。 - ComFreek
@ComFreek哦,我明白了。不知道这很重要。 - StudioWorks


这是一个两部分问题:

如何迭代Map的条目  - @ ScArcher2有 回答 完美的。

迭代的顺序是什么  - 如果你刚刚使用 Map,严格来说,有 没有订购保证。所以你不应该真正依赖任何实现给出的顺序。但是,那 SortedMap界面扩展 Map 并提供您正在寻找的内容 - 实现将提供一致的排序顺序。

NavigableMap 是另一个有用的扩展 - 这是一个 SortedMap 使用其他方法按键集中的有序位置查找条目。因此,这可能会消除首先迭代的需要 - 您可能能够找到具体的 entry 你在使用之后 higherEntrylowerEntryceilingEntry, 要么 floorEntry 方法。该 descendingMap 方法甚至给你一个明确的方法 颠倒遍历顺序


76
2017-09-05 22:15





迭代地图有几种方法。

这里是通过在地图中存储一百万个键值对来比较它们在地图中存储的公共数据集的性能,并将迭代在地图上。

1)使用 entrySet() 在每个循环中

for (Map.Entry<String,Integer> entry : testMap.entrySet()) {
    entry.getKey();
    entry.getValue();
}

50毫秒

2)使用 keySet() 在每个循环中

for (String key : testMap.keySet()) {
    testMap.get(key);
}

76毫秒

3)使用 entrySet() 和迭代器

Iterator<Map.Entry<String,Integer>> itr1 = testMap.entrySet().iterator();
while(itr1.hasNext()) {
    Map.Entry<String,Integer> entry = itr1.next();
    entry.getKey();
    entry.getValue();
}

50毫秒

4)使用 keySet() 和迭代器

Iterator itr2 = testMap.keySet().iterator();
while(itr2.hasNext()) {
    String key = itr2.next();
    testMap.get(key);
}

75毫秒

我提到了 this link


62
2018-02-05 06:42



很好的例子!我更喜欢用#1的方式。 - Phuong


仅供参考,您也可以使用 map.keySet() 和 map.values() 如果你只对地图的键/值感兴趣而不是对另一个感兴趣。


41
2017-09-05 22:27





正确的方法是使用接受的答案,因为它是最有效的。我发现以下代码看起来更清晰。

for (String key: map.keySet()) {
   System.out.println(key + "/" + map.get(key));
}

40
2017-09-07 19:48



这不是最好的方法,使用entrySet()更有效。 Findbugs将标记此代码(请参阅 findbugs.sourceforge.net/...) - Jeff Olson
@JeffOlson嗯,不是真的。 map lookup是O(1),因此两个循环的行为方式相同。不可否认,在微观基准测试中它会略微变慢,但我有时也这样做,因为我讨厌一遍又一遍地写出类型参数。这也很可能永远不会成为你的性能瓶颈,所以如果它使代码更具可读性,那就去做吧。 - kritzikratzi
更详细: O(1) = 2*O(1) 几乎是大O符号的定义。你是对的,因为它运行得慢一些,但就复杂性而言,它们是相同的。 - kritzikratzi
通过碰撞与否,我的意思是,如果你碰撞几次并不重要,显然如果你只有碰撞,这是一个不同的故事。所以你很小气,但是,你所说的是真的。 - kritzikratzi
@Jeff Olson:当只有一个常数因素时,“大O”复杂性没有改变的评论是正确的。不过,对我而言,操作需要一个小时还是两个小时。更重要的是,必须强调的是因素 不  2,作为迭代 entrySet() 根本没有查找;它只是所有条目的线性遍历。相比之下,迭代 keySet() 每个键执行查找 一 查找每个键,所以我们讨论零查找与 ñ 在这里查找, ñ 是的大小 Map。所以这个因素远远超出了 2... - Holger