如何有效地迭代 Java Map 中的每个条目?

问题描述:

如果我有一个在 Java 中实现 Map 接口的对象,并且我希望遍历其中包含的每一对,那么遍历映射的最有效方法是什么?

元素的顺序是否取决于我对接口的特定地图实现?

解决方案1:

huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感

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

在 Java 10+ 上:

for (var entry : map.entrySet()) {
    System.out.println(entry.getKey() + "/" + entry.getValue());
}

如果你这样做,那么它将不起作用,因为 Entry 是 Map 中的嵌套类。 java.sun.com/javase/6/docs/api/java/util/Map.html

您可以将导入写为“import java.util.Map.Entry;”它会起作用。

@Pureferret 您可能想要使用迭代器的唯一原因是如果您需要调用它的 remove 方法。如果是这种情况,this other answer 将向您展示如何操作。否则,如上面答案所示的增强循环是要走的路。

我相信 Map.Entry 形式比将内部类导入当前命名空间更清晰。

请注意,如果您只想遍历值或键,则可以使用 map.values() 或 map.keySet()。

解决方案2:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

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

使用迭代器和 Map.Entry long i = 0; Iterator> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry pair = it.next(); i += pair.getKey() + pair.getValue();使用 foreach 和 Map.Entry long i = 0; for (Map.Entry pair : map.entrySet()) { i += pair.getKey() + pair.getValue();使用 Java 8 中的 forEach final long[] i = {0}; map.forEach((k, v) -> i[0] += k + v);使用 keySet 和 foreach long i = 0; for (Integer key : map.keySet()) { i += key + map.get(key);使用 keySet 和迭代器 long i = 0;迭代器<整数> itr2 = map.keySet().iterator(); while (itr2.hasNext()) { 整数键 = itr2.next(); i += 键 + map.get(key);使用 for 和 Map.Entry long i = 0; for (Iterator> entries = map.entrySet().iterator(); entries.hasNext(); ) { Map.Entry entry = entries.next(); i += entry.getKey() + entry.getValue(); } 使用 Java 8 Stream API final long[] i = {0}; map.entrySet().stream().forEach(e -> i[0] += e.getKey() + e.getValue());使用 Java 8 Stream API 并行 final long[] i = {0}; map.entrySet().stream().parallel().forEach(e -> i[0] += e.getKey() + e.getValue());使用 Apache Collections 的 IterableMap long i = 0; MapIterator it = iterableMap.mapIterator(); while (it.hasNext()) { i += it.next() + it.getValue();使用 Eclipse (CS) 的 MutableMap 集合 final long[] i = {0}; mutableMap.forEachKeyValue((key, value) -> { i[0] += key + value; });

性能测试(模式 = AverageTime,系统 = Windows 8.1 64 位,Intel i7-4790 3.60 GHz,16 GB)

对于小地图(100个元素),得分为0.308是最佳基准模式CNT分数错误单元test3_usishandForeachAndJava8 AVGT 10 0.308±0.021 µs/op test10_usistereclipsemap avgt 10 0.309±0.009 µs/op test1_usit1_usis1_Usist1_Usist1_Usist1_UsishiLeandmmapenter Avgt 10.380±0.380±0.380±0.380±0.380± 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 For a map with 10000 elements, score 37.606 is the best 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 平均 10 51.406 ± 1.032 µs/op test1_UsingWhileAndMapEntry 平均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对于具有100000个元素的地图,得分为1184.767是最佳基准模式CNT分数错误单元test1_usishileandmapentry avgt 10 1184.767±332.968 µs/op test10_usistereclipseclipsemap avggt 10 1191.735±304.273 µs/optry 8 33.35; 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/操作

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

https://i.stack.imgur.com/17VGh.png

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

          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 上。

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

@Viacheslav:非常好的答案。只是想知道在您的基准测试中如何通过捕获 lambdas 来阻碍 Java8 api...(例如 long sum = 0; map.forEach( /* accumulate in variable sum*/); 捕获 sum 长,这可能比例如 stream.mapToInt(/*whatever*/).sum 慢。当然,您不能总是避免捕获状态,但这可能是对替补席的合理补充。

@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 之间存在分歧,您知道这些测试结果毫无意义。

比较 3 个主要实现怎么样:HashMap、LinkedHashMap 和 TreeMap?

#1 和 #6 完全相同。使用 while 与 for 循环并不是一种不同的迭代技术。我很惊讶他们在你的测试中会有这样的差异——这表明测试没有正确地与与你打算测试的事物无关的外部因素隔离开来。

#8 是一个糟糕的例子,因为 parallel 现在添加到 i 时会出现竞争条件。

解决方案3:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

在 Java 8 中,您可以使用新的 lambdas 功能来干净、快速地完成它:

 Map 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。

十分简单!

根据您要对地图执行的操作,您还可以对 map.entrySet().stream() docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html 返回的条目使用流 API

如果您想从 forEach() 中引用在 lambda 表达式之外声明的非最终变量,这将不起作用...

@Chris 正确。如果您尝试从 lambda 外部有效地使用非最终变量,它将不起作用。

解决方案4:

huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求

是的,顺序取决于具体的 Map 实现。

@ScArcher2 has the more elegant Java 1.5 syntax。在 1.4 中,我会做这样的事情:

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

比 while.. for(Iterator entries = myMap.entrySet().iterator(); entries.hasNext(); ) {...} 更喜欢 for 循环.

@jpredham您是对的,将 for 构造用作 for (Entry e : myMap.entrySet) 将不允许您修改集合,但 @HanuAthena 提到的示例应该可以工作,因为它为您提供了范围内的 Iterator。 (除非我遗漏了什么……)

IntelliJ 在 Entry thisEntry = (Entry) entries.next(); 上给我错误:无法识别 Entry。那是别的东西的伪代码吗?

@JohnK 尝试导入 java.util.Map.Entry。

如果您有整数键和字符串键,此解决方案将不起作用。

解决方案5:

huntsbot.com提供全网独家一站式外包任务、远程工作、创意产品分享与订阅服务!

迭代地图的典型代码是:

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

HashMap 是规范的 map 实现,不做任何保证(或者如果没有对其执行变异操作,它不应该更改顺序)。 SortedMap 将根据键的自然顺序或 Comparator(如果提供)返回条目。 LinkedHashMap 将根据其构造方式以插入顺序或访问顺序返回条目。 EnumMap 以键的自然顺序返回条目。

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

EnumMap 和 IdentityHashMap 也有这种特殊的行为

“LinkedHashMap 将返回 [...] 访问顺序 [...] 中的条目” ...所以您按访问顺序访问元素?要么是重言式,要么是一些有趣的东西,可以使用题外话。 ;-)

@jpaugh 仅直接访问 LinkedHashMap 计数。通过iterator、spliterator、entrySet等,不修改顺序。

1.虽然→如果? 2. 最后一段可能会从复习中受益。

解决方案6:

与HuntsBot一起,探索全球自由职业机会–huntsbot.com

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

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

您应该将 Iterator 放在 for 循环中以限制其范围。

@SteveKuo“限制其范围”是什么意思?

@StudioWorks for (Iterator> entries = myMap.entrySet().iterator(); entries.hasNext(); ) { Map.Entry entry = entries.next(); }。通过使用该构造,我们将(变量的可见性)entries 的范围限制为 for 循环。

解决方案7:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

这是一个两部分的问题:

如何迭代 Map 的条目 - @ScArcher2 answered 完美。

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

NavigableMap 是另一个有用的扩展 - 这是一个 SortedMap,它具有额外的方法,用于根据条目在键集中的有序位置查找条目。因此,这可能会首先消除迭代的需要 - 您可能能够在使用 higherEntry、lowerEntry、ceilingEntry 或 floorEntry 方法后找到您所在的特定 entry。 descendingMap 方法甚至为您提供了反转遍历顺序 的显式方法。

解决方案8:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

有几种方法可以遍历 map。

这是通过在 map 中存储一百万个键值对并将迭代 map 来比较它们对存储在 map 中的常见数据集的性能。

  1. 在每个循环中使用 entrySet()
for (Map.Entry entry : testMap.entrySet()) {
    entry.getKey();
    entry.getValue();
}

50 毫秒

  1. 在每个循环中使用 keySet()
for (String key : testMap.keySet()) {
    testMap.get(key);
}

76 毫秒

  1. 使用 entrySet() 和迭代器
Iterator> itr1 = testMap.entrySet().iterator();
while(itr1.hasNext()) {
    Map.Entry entry = itr1.next();
    entry.getKey();
    entry.getValue();
}

50 毫秒

  1. 使用 keySet() 和迭代器
Iterator itr2 = testMap.keySet().iterator();
while(itr2.hasNext()) {
    String key = itr2.next();
    testMap.get(key);
}

75 毫秒

我提到了this link。

运行时间取自未使用 Java Microbenchmarking Harness 的文章。因此,时间是不可靠的,因为代码可能已经被 JIT 编译器完全优化。

解决方案9:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

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

解决方案10:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

正确的方法是使用公认的答案,因为它是最有效的。我发现下面的代码看起来更干净一些。

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

这不是最好的方法,使用 entrySet() 效率更高。 Findbugs 将标记此代码(请参阅 findbugs.sourceforge.net/…)

@JeffOlson meh,不是真的。地图查找是 O(1),因此两个循环的行为方式相同。诚然,在微基准测试中它会稍微慢一些,但我有时也会这样做,因为我讨厌一遍又一遍地编写类型参数。此外,这很可能永远不会成为您的性能瓶颈,所以如果它使代码更具可读性,那就去吧。

更详细:O(1) = 2*O(1) 几乎是大 O 符号的定义。你是对的,它运行得有点慢,但就复杂性而言,它们是相同的。

@Jeff Olson:当只有一个常数因素时,“Big O”复杂性不会改变的评论是正确的。不过,对我来说,手术需要一小时还是两小时很重要。更重要的是,必须强调该因子不是 2,因为遍历 entrySet() 根本不需要查找;它只是所有条目的线性遍历。相比之下,遍历 keySet() 并执行每个键的查找需要每个键 一个 查找,因此我们在这里讨论零查找与 n 查找,< i>n 是 Map 的大小。所以这个因素远远超出了2......

@kornero:可能值得注意的是,您不需要密钥具有相同的哈希码即可发生冲突;当 hashcode % capacity 相同时已经发生冲突。从 Java 8 开始,具有相同 hashcode % capacity 但不同 hashcode 或为 Comparable 的项目的复杂性回落到 O(log n) 并且只有具有相同哈希码且不是 Comparable 的键强加 O(n)复杂。但实际上查找的复杂度可能超过 O(1) 的说法仍然成立。

解决方案11:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

使用 Java 8,您可以使用 forEach 和 lambda 表达式迭代 Map,

map.forEach((k, v) -> System.out.println((k + ":" + v)));

原文链接:https://www.huntsbot.com/qa/Zv9v/how-do-i-efficiently-iterate-over-each-entry-in-a-java-map?lang=zh_CN

打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值