阿里面试官:让我说说HashMap的循环?

简介: 面试问我HashMap循环,我一口气给干了七种?

hashMap 应该是java程序员工作中用的比较多的一个键值对处理的数据的类型了。这种数据类型一般都会有增删查的方法,今天我们就来看看它的循环方法以前写过一篇关于ArrayList的循环效率问题《ArrayList哪种遍历效率最好,你真的弄明白了吗?》,感兴趣的同学可以去看看。hashMap 有常见的六七种遍历的方式。这么多的选择,大家平时都是使用哪一种来遍历数据列?欢迎大家在下方留言哦。说实话这么多种方式,想记也不记不住,也不想浪费时间来记这玩意,所以本人在JDK1.8以前基本上都是用Map.Entry的方式来遍历,1.8及以后就习惯性用forEach了,不过这个不能有continue或者break操作这个有时候还是挺不方便的,其他几种基本上没怎么用过,也没太研究这几种方式,哪种性能是比较好的。反正就是挑自己熟悉的方式。好了话不多说,我们还是直入今天的主题。先来看看每种遍历的方式:

在for循环中使用entries实现Map的遍历

public static void forEachEntries() {

    for (Map.Entry<String, String> entry : map.entrySet()) {
        String mapKey = entry.getKey();
        String mapValue = entry.getValue();
    }
}
在for循环中遍历key

public static void forEachKey() {

    for (String key : map.keySet()) {
        String mapKey = key;
        String mapValue = map.get(mapKey);
    }
}
在for循环中遍历value

public static void forEachValues() {

    for (String key : map.values()) {
        String val = key;
    }
}

Iterator遍历

public static void forEachIterator() {

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

forEach jdk1.8遍历

public static void forEach() {

    map.forEach((key, val) -> {
        String key1 = key;
        String value = val;
    });
}

Stream jdk1.8遍历

map.entrySet().stream().forEach((entry) -> {

        String key = entry.getKey();
        String value = entry.getValue();
    });

Streamparallel jdk1.8遍历

public static void forEachStreamparallel() {

    map.entrySet().parallelStream().forEach((entry) -> {
        String key = entry.getKey();
        String value = entry.getValue();
    });
}

以上就是常见的对于map的一些遍历的方式,下面我们来写个测试用例来看下这些遍历方式,哪些是效率最好的。下面测试用例是基于JMH来测试的
首先引入pom

 <dependency>
        <groupId>org.openjdk.jmh</groupId>
        <artifactId>jmh-core</artifactId>
        <version>1.23</version>
    </dependency>
    <dependency>
        <groupId>org.openjdk.jmh</groupId>
        <artifactId>jmh-generator-annprocess</artifactId>
        <version>1.23</version>
        <scope>provided</scope>
    </dependency>

关于jmh测试如可能会影响结果的一些因素这里就不详细介绍了,可以参考文末的第一个链接写的非常详细。以及测试用例为什么要这么写(都是为了消除JIT对测试代码的影响)这是参照官网的链接
编写测试代码如下:

package com.workit.autoconfigure.autoconfigure.controller;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**

  • @author:公众号: java金融
  • @Date:
  • @Description:微信搜一搜【java金融】回复666
    */

@State(Scope.Thread)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class InstructionsBenchmark {

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder().include(InstructionsBenchmark.class.getSimpleName()).result("result.json").resultFormat(ResultFormatType.JSON).build();
    new Runner(opt).run();
}

static final int BASE = 42;

static int add(int key,int val) {
  return  BASE + key +val;
}
@Param({"1", "10", "100", "1000","10000","100000"})
int size;
private static Map<Integer, Integer>  map;

// 初始化方法,在全部Benchmark运行之前进行
@Setup(Level.Trial)
public void init() {
    map = new HashMap<>(size);
    for (int i = 0; i < size; i++) {
        map.put(i, i);
    }
}
/**
 * 在for循环中使用entries实现Map的遍历:
 */
@Benchmark
public static void forEachEntries(Blackhole blackhole) {
    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
        Integer mapKey = entry.getKey();
        Integer mapValue = entry.getValue();
        blackhole.consume(add(mapKey,mapValue));
    }
}

/**
 * 在for循环中遍历key
 */
@Benchmark
public static StringBuffer forEachKey(Blackhole blackhole) {
    StringBuffer stringBuffer = new StringBuffer();
    for (Integer key : map.keySet()) {
      //  Integer mapValue = map.get(key);
        blackhole.consume(add(key,key));
    }
    return stringBuffer;
}

/**
 * 在for循环中遍历value
 */
@Benchmark
public static void forEachValues(Blackhole blackhole) {
    for (Integer key : map.values()) {
        blackhole.consume(add(key,key));
    }
}

/**
 * Iterator遍历;
 */
@Benchmark
public static void forEachIterator(Blackhole blackhole) {
    Iterator<Entry<Integer, Integer>> entries = map.entrySet().iterator();
    while (entries.hasNext()) {
        Entry<Integer, Integer> entry = entries.next();
        Integer key = entry.getKey();
        Integer value = entry.getValue();
        blackhole.consume(add(key,value));
    }
}

/**
 * forEach jdk1.8遍历
 */
@Benchmark
public static void forEachLamada(Blackhole blackhole) {
    map.forEach((key, value) -> {
        blackhole.consume(add(key,value));
    });

}

/**
 * forEach jdk1.8遍历
 */
@Benchmark
public static void forEachStream(Blackhole blackhole) {
    map.entrySet().stream().forEach((entry) -> {
        Integer key = entry.getKey();
        Integer value = entry.getValue();
        blackhole.consume(add(key,value));

    });
}

@Benchmark
public static void forEachStreamparallel(Blackhole blackhole) {
    map.entrySet().parallelStream().forEach((entry) -> {
        Integer key = entry.getKey();
        Integer value = entry.getValue();
        blackhole.consume(add(key,value));

    });
}

}

运行结果如下:
注:运行环境idea 2019.3,jdk1.8,windows7 64位。

Benchmark (size) Mode Cnt Score Error Units
InstructionsBenchmark.forEachEntries 1 avgt 5 10.021 ± 0.224 ns/op
InstructionsBenchmark.forEachEntries 10 avgt 5 71.709 ± 2.537 ns/op
InstructionsBenchmark.forEachEntries 100 avgt 5 738.873 ± 12.132 ns/op
InstructionsBenchmark.forEachEntries 1000 avgt 5 7804.431 ± 136.635 ns/op
InstructionsBenchmark.forEachEntries 10000 avgt 5 88540.345 ± 14915.682 ns/op
InstructionsBenchmark.forEachEntries 100000 avgt 5 1083347.001 ± 136865.960 ns/op
InstructionsBenchmark.forEachIterator 1 avgt 5 10.675 ± 2.532 ns/op
InstructionsBenchmark.forEachIterator 10 avgt 5 73.934 ± 4.517 ns/op
InstructionsBenchmark.forEachIterator 100 avgt 5 775.847 ± 198.806 ns/op
InstructionsBenchmark.forEachIterator 1000 avgt 5 8905.041 ± 1294.618 ns/op
InstructionsBenchmark.forEachIterator 10000 avgt 5 98686.478 ± 10944.570 ns/op
InstructionsBenchmark.forEachIterator 100000 avgt 5 1045309.216 ± 36957.608 ns/op
InstructionsBenchmark.forEachKey 1 avgt 5 18.478 ± 1.344 ns/op
InstructionsBenchmark.forEachKey 10 avgt 5 76.398 ± 12.179 ns/op
InstructionsBenchmark.forEachKey 100 avgt 5 768.507 ± 23.892 ns/op
InstructionsBenchmark.forEachKey 1000 avgt 5 11117.896 ± 1665.021 ns/op
InstructionsBenchmark.forEachKey 10000 avgt 5 84871.880 ± 12056.592 ns/op
InstructionsBenchmark.forEachKey 100000 avgt 5 1114948.566 ± 65582.709 ns/op
InstructionsBenchmark.forEachLamada 1 avgt 5 9.444 ± 0.607 ns/op
InstructionsBenchmark.forEachLamada 10 avgt 5 76.125 ± 5.640 ns/op
InstructionsBenchmark.forEachLamada 100 avgt 5 861.601 ± 98.045 ns/op
InstructionsBenchmark.forEachLamada 1000 avgt 5 7769.714 ± 1663.914 ns/op
InstructionsBenchmark.forEachLamada 10000 avgt 5 73250.238 ± 6032.161 ns/op
InstructionsBenchmark.forEachLamada 100000 avgt 5 836781.987 ± 72125.745 ns/op
InstructionsBenchmark.forEachStream 1 avgt 5 29.113 ± 3.275 ns/op
InstructionsBenchmark.forEachStream 10 avgt 5 117.951 ± 13.755 ns/op
InstructionsBenchmark.forEachStream 100 avgt 5 1064.767 ± 66.869 ns/op
InstructionsBenchmark.forEachStream 1000 avgt 5 9969.549 ± 342.483 ns/op
InstructionsBenchmark.forEachStream 10000 avgt 5 93154.061 ± 7638.122 ns/op
InstructionsBenchmark.forEachStream 100000 avgt 5 1113961.590 ± 218662.668 ns/op
InstructionsBenchmark.forEachStreamparallel 1 avgt 5 65.466 ± 5.519 ns/op
InstructionsBenchmark.forEachStreamparallel 10 avgt 5 2298.999 ± 721.455 ns/op
InstructionsBenchmark.forEachStreamparallel 100 avgt 5 8270.759 ± 1801.082 ns/op
InstructionsBenchmark.forEachStreamparallel 1000 avgt 5 16049.564 ± 1972.856 ns/op
InstructionsBenchmark.forEachStreamparallel 10000 avgt 5 69230.849 ± 12169.260 ns/op
InstructionsBenchmark.forEachStreamparallel 100000 avgt 5 638129.559 ± 14885.962 ns/op
InstructionsBenchmark.forEachValues 1 avgt 5 9.743 ± 2.770 ns/op
InstructionsBenchmark.forEachValues 10 avgt 5 70.761 ± 16.574 ns/op
InstructionsBenchmark.forEachValues 100 avgt 5 745.069 ± 329.548 ns/op
InstructionsBenchmark.forEachValues 1000 avgt 5 7772.584 ± 1702.295 ns/op
InstructionsBenchmark.forEachValues 10000 avgt 5 74063.468 ± 23752.678 ns/op
InstructionsBenchmark.forEachValues 100000 avgt 5 994057.370 ± 279310.867 ns/op

在这里插入图片描述
在这里插入图片描述

通过上述的图我们可以发现,数据量较小的时候forEachEntriesforEachIterator、以及lamada循环效率都差不多forEachStreamarallel的效率反而较低,只有当数据量达到10000以上parallelStream的优势就体现出来了。所以平时选择使用哪种循环方式的时候没必要太纠结哪一种方式,其实每种方式之间的效率还是微乎其微的。选择适合自己的就好。为什么parallelStream在数据量较小的时候效率反而不行?这个大家可以在下方留言哦。

总结

上面小实验只是在我机器上跑出来的结果,可能放到不同的机器运行结果有不一样哦,大家感兴趣的同学可以把代码贴到自己的机器上跑一跑,也许我这这个结论就不适用了。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。
    在这里插入图片描述

巨人的肩膀摘苹果:
https://www.cnkirito.moe/java-jmh/
https://jmh.morethan.io/

目录
相关文章
|
2月前
|
存储 关系型数据库 MySQL
阿里面试:为什么要索引?什么是MySQL索引?底层结构是什么?
尼恩是一位资深架构师,他在自己的读者交流群中分享了关于MySQL索引的重要知识点。索引是帮助MySQL高效获取数据的数据结构,主要作用包括显著提升查询速度、降低磁盘I/O次数、优化排序与分组操作以及提升复杂查询的性能。MySQL支持多种索引类型,如主键索引、唯一索引、普通索引、全文索引和空间数据索引。索引的底层数据结构主要是B+树,它能够有效支持范围查询和顺序遍历,同时保持高效的插入、删除和查找性能。尼恩还强调了索引的优缺点,并提供了多个面试题及其解答,帮助读者在面试中脱颖而出。相关资料可在公众号【技术自由圈】获取。
|
9天前
|
存储 NoSQL 架构师
阿里面试:聊聊 CAP 定理?哪些中间件是AP?为什么?
本文深入探讨了分布式系统中的“不可能三角”——CAP定理,即一致性(C)、可用性(A)和分区容错性(P)三者无法兼得。通过实例分析了不同场景下如何权衡CAP,并介绍了几种典型分布式中间件的CAP策略,强调了理解CAP定理对于架构设计的重要性。
36 4
|
27天前
|
存储 NoSQL 算法
阿里面试:亿级 redis 排行榜,如何设计?
本文由40岁老架构师尼恩撰写,针对近期读者在一线互联网企业面试中遇到的高频面试题进行系统化梳理,如使用ZSET排序统计、亿级用户排行榜设计等。文章详细介绍了Redis的四大统计(基数统计、二值统计、排序统计、聚合统计)原理和应用场景,重点讲解了Redis有序集合(Sorted Set)的使用方法和命令,以及如何设计社交点赞系统和游戏玩家排行榜。此外,还探讨了超高并发下Redis热key分治原理、亿级用户排行榜的范围分片设计、Redis Cluster集群持久化方式等内容。文章最后提供了大量面试真题和解决方案,帮助读者提升技术实力,顺利通过面试。
|
1月前
|
SQL 关系型数据库 MySQL
阿里面试:1000万级大表, 如何 加索引?
45岁老架构师尼恩在其读者交流群中分享了如何在生产环境中给大表加索引的方法。文章详细介绍了两种索引构建方式:在线模式(Online DDL)和离线模式(Offline DDL),并深入探讨了 MySQL 5.6.7 之前的“影子策略”和 pt-online-schema-change 方案,以及 MySQL 5.6.7 之后的内部 Online DDL 特性。通过这些方法,可以有效地减少 DDL 操作对业务的影响,确保数据的一致性和完整性。尼恩还提供了大量面试题和解决方案,帮助读者在面试中充分展示技术实力。
|
2月前
|
消息中间件 存储 canal
阿里面试:canal+MQ,会有乱序的问题吗?
本文详细探讨了在阿里面试中常见的问题——“canal+MQ,会有乱序的问题吗?”以及如何保证RocketMQ消息有序。文章首先介绍了消息有序的基本概念,包括全局有序和局部有序,并分析了RocketMQ中实现消息有序的方法。接着,针对canal+MQ的场景,讨论了如何通过配置`canal.mq.partitionsNum`和`canal.mq.partitionHash`来保证数据同步的有序性。最后,提供了多个与MQ相关的面试题及解决方案,帮助读者更好地准备面试,提升技术水平。
阿里面试:canal+MQ,会有乱序的问题吗?
|
2月前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
64 5
|
2月前
|
消息中间件 架构师 Java
阿里面试:秒杀的分布式事务, 是如何设计的?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试阿里、滴滴、极兔等一线互联网企业时,遇到了许多关于分布式事务的重要面试题。为了帮助大家更好地应对这些面试题,尼恩进行了系统化的梳理,详细介绍了Seata和RocketMQ事务消息的结合,以及如何实现强弱结合型事务。文章还提供了分布式事务的标准面试答案,并推荐了《尼恩Java面试宝典PDF》等资源,帮助大家在面试中脱颖而出。
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
|
2月前
|
Kubernetes 架构师 算法
阿里面试:全国14亿人,统计出重名最多的前100个姓名
文章介绍了如何解决“从全国14亿人的数据中统计出重名人数最多的前100位姓名”的面试题,详细分析了多种数据结构的优缺点,最终推荐使用前缀树(Trie)+小顶堆的组合。文章还提供了具体的Java代码实现,并讨论了在内存受限情况下的解决方案,强调了TOP N问题的典型解题思路。最后,鼓励读者通过系统化学习《尼恩Java面试宝典》提升面试技巧。
阿里面试:全国14亿人,统计出重名最多的前100个姓名
|
2月前
|
存储 缓存 NoSQL
阿里面试题:缓存的一些常见的坑,你遇到过哪些,怎么解决的?
阿里面试题:缓存的一些常见的坑,你遇到过哪些,怎么解决的?