HashMap遍历效率最高的方法是哪种?

后端 潘老师 7个月前 (10-13) 148 ℃ (0) 扫码查看

这篇Java教程讨论了不同遍历HashMap的方式,并比较了每种方式的性能,以便我们知道HashMap遍历效率最高的方法是哪种。

1.引言

在这篇文章中,我决定比较不同遍历HashMap的方式的性能。HashMap是一个广泛使用的类,大多数情况下,我们使用该类提供的get(Object key)方法来获取值。但有时需要遍历整个Map并获取其中存储的所有键-值对。

例如,分析从客户端发送的所有请求参数。在这种情况下,对于每个客户端,我们至少在请求处理期间遍历整个映射。

如果我们在代码中的许多地方都使用这种类型的迭代,而且有许多请求,那么我们肯定希望优化HashMap迭代代码,以充分利用它。我下面的分析将帮助我们决定下一步该做什么。

2.遍历Map的不同方式

让我们从不同遍历HashMap的方式开始。HashMap定义如下:

Map<String, Integer> map = new HashMap();

2.1. HashMap.forEach()

自Java 8以来,我们可以使用forEach()方法遍历Map中存储的键和值。

map.forEach((key, value) -> {
    System.out.println(key + ": " + value);
    //...
});

2.2. 遍历Map的条目

entrySet()方法返回一个Set视图,其中包含所有的条目,我们可以像普通的HashSet一样遍历它。

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

2.3. 遍历Map的键并访问值

keySet()方法返回所有键的集合。我们可以遍历所有键,并使用它们来访问Map中的值。请注意,这需要额外的步骤来访问值,所以在大多数情况下,这不是推荐的方法。

for (String key : map.keySet()) {
  Integer value = map.get(key);
  //...
}

2.4. 使用迭代器

由于我们从entrySet()和keySet()获取Set视图,因此我们可以使用迭代器遍历Map。

以下代码使用迭代器遍历Map条目。

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

同样,以下代码使用迭代器遍历Map的键,并使用键来访问值。

Iterator<String> keySetIterator = map.keySet().iterator();
while (keySetIterator.hasNext()) {
  String key = keySetIterator.next();
  Integer value = map.get(key);
  //...
}

3.比较不同技术的性能

现在让我们比较对Map中存储的常见数据集执行所有类型的遍历的性能。我们在Map中存储了100万个键-值对。

Map<String, Integer> map = new HashMap();
for (int i = 0; i < 10_00_000; i++) {
  map.put(String.valueOf(i), i);
}

我们将以四种方式遍历Map。我们还将以最合适的方式从Map中获取所有条目的键和值。我们使用JMH模块来对所有方法的性能进行基准测试。

@Benchmark
public void usingForEach(Blackhole blackhole) {
  map.forEach((key, value) -> {
    blackhole.consume(key);
    blackhole.consume(value);
  });
}
@Benchmark
public void usingEntrySetWithForLoop(Blackhole blackhole) {
  for (Map.Entry<String, Integer> entry : map.entrySet()) {
    blackhole.consume(entry.getKey());
    blackhole.consume(entry.getValue());
  }
}
@Benchmark
public void usingKeySetWithForLoop(Blackhole blackhole) {
  for (String key : map.keySet()) {
    blackhole.consume(map.get(key));
  }
}
@Benchmark
public void usingEntrySetWithForIterator(Blackhole blackhole) {
  Iterator<Entry<String, Integer>> entryIterator = map.entrySet().iterator();
  while (entryIterator.hasNext()) {
    Map.Entry<String, Integer> entry = entryIterator.next();
    blackhole.consume(entry.getKey());
    blackhole.consume(entry.getValue());
  }
}

4.结果

以上程序的基准测试得分是:

c.h.c.collections.map...usingForEach                     thrpt   15    89.044 ±   2.703  ops/s
c.h.c.collections.map...usingEntrySetWithForLoop         thrpt   15    54.906 ±   6.326  ops/s
c.h.c.collections.map...usingKeySetWithForLoop          thrpt   15    52.163 ±   4.517  ops/s
c.h.c.collections.map...usingEntrySetWithForIterator      thrpt   15    63.494 ±   4.334  ops/s

虽然所有方法之间的差异不大,但我们可以看到:

  • 使用for循环与keySet()、entrySet()方法几乎表现相同。
  • 使用iterator()方法性能较差。
  • 令人惊讶的是,forEach()方法需要最长的时间。这是因为forEach方法使用内部迭代,这意味着迭代逻辑在HashMap实现内部处理。它为HashMap中的每个元素调用提供的lambda表达式。这种内部迭代会带来一些开销,因为涉及到lambda表达式的执行和额外的函数调用。

出于代码的清晰性,我们可以得出以下结论:

  • 如果Map仅包含少量条目,则使用HashMap.forEach()是遍历HashMap的最佳方式。
  • 如果Map包含100万个条目,那么使用HashMap.entrySet()是遍历HashMap的最佳方式。

5.结论

对于大多数应用需求来说,100万是一个非常大的数字。尽管与毫秒级的差异相比,并不是非常显著,但与for循环的情况相比,差异很大。我认为大多数人可以容忍这种微小的差异。

但如果你想得出结论,使用entry set非常明确,并且比其他方法性能更好。当多次执行上述程序时,结果的变化在20%到50%之间。

扩展阅读:Java中多种循环性能的区别比较


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/9528.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】